How to change the appearance of blocks located in ngFor? - javascript

I have an article-map component. In it, with the help of the ngFor directive, I display brief information about the articles. I call the "article-map" component in the "feed" component. The "feed" component should have three views with different "article-map" styling. For example: the first one is a sheet, the second one is two columns, etc. How do I style the article-map component for different feed views using ngClass?
I started with the following. In article-card.component.ts I set a variable to get the parameter from the link.
export class ArticleCardComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
typeView='default';
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
console.log(params)
this.typeView=params['viewType'];
})
}
}
Now I want to write a ternary expression for ngClass that will allow different style classes to be used for feed views. Tell me how to do it?

In your case it will be.
<div [ngClass]="typeView === 'default' ? 'css-class-1' : 'css-class-2'">
If you want more than one condition write a function like
html
<div [ngClass]="getClass()">
ts
getClass() {
switch(this.typeView) {
case 'default':
return 'css-class-1';
case 'special':
return 'css-class-2';
default:
return 'css-class';
}
}

Related

How to update view from another component in Angular 7?

I'd like to refresh my card set from navigation bar which is part of app.component.html so I prepared refresh() function.
When it is called it does update variable Cards but doesn't render it in ngFor on html element in mainView.html.
It does render updated set if I call from html element in mainView.html (as (click)="loadCards()") but not if the same ((click)="refresh()") is done in app.component.html.
export class MainView implements OnInit {
constructor(private mMainController: MainController) {}
Cards: any = [];
ngOnInit() {
this.loadCards();
}
loadCards() {
this.mMainController.getAllCards().subscribe(
(data) => {this.Cards = data); },
(error) => {},
() => {console.log(this.Cards));
}
...
}
export class AppComponent {
...
constructor(private router: Router, private mMainView: MainView) {}
refresh(){
console.log('done');
this.mMainView.loadCards();
}
...
}
Update
Tried with #Input() but couldn't get it work. I implemented RefreshService as explained in accepted answer and now I'm able to refresh content from other components.
Thank you all for quick response.
FIST WAY: USING A SHARED SERVICE
You need to introduce a service that manage the state of your car.
In this case it may be usefull to introduce for this a BehaviorSubject like this:
Your Service:
private refresh: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public getRefresh(): Observable<boolean> {
return this.refresh.asObservable();
}
public setRefresh(value: boolean): void {
this.refresh.next(value);
}
Inside your MainView class
First: inject your service as dependency
Second: Subscribe to your observable inside OnInit hook e.g like this:
this.myService.getRefresh().subscribe((value: boolean) => {
if(value) {
this.loadCards()
}
})
Inside your AppComponent class
First: inject your service as dependency
Second: Set the value of your observable inside your refresh method.
e.g something like this:
public refresh(){
this.myService.setRefresh(true);
}
SECOND WAY: USING #Input Decorator to pass value down.
You're attempting to use MainView as a dependency but it's not an injectable dependency. Try to use inputs/outputs between app component and MainView, if possible. If MainView isn't a child of AppComponent then abstract the logic for loading cards into a service and inject it into both.
You can implement the component interaction in two ways
(i) If the components are related to each other use the common and straightforward method of sharing data. It works by using the #Input() decorator to allow data to be passed via the template.
(ii) If the components are not related to each other you can use a shared service using subject to communicate between the two components

Angular - dynamically destroying a component, *ngIf and ComponentRef

I'm making a pop-up component that I want to use in several of my other components, so I made a popup.service that enable the component to be loaded through *ngIf inside other components. This is creating a problem for me since the PopupComponent is a separate entity and I'm unsure how to pass data from the child component(PopupComponent) to its respective parents.
Atm the loading looks like this in ParentComponent.ts:
public openPopup(order_id: string, invoice_id: string): void{
this.load_popup=this.popupService.openPopup(order_id, "selected_order", invoice_id, "selected_invoice");
}
And ParentComponent.html:
<app-popup *ngIf="load_popup"></app-popup>
And it loads like a charm, the problem is in closing it. The close button is located on the PopupComponent, is there an efficient way to have the Child Component (PopupComponent) to affect a variable in the Parent Component ie. ParentComponent.load_popup=false?
My other thought was dynamically loading the component, however I have no idea on how to do that. I was fidgeting around with using the PopupService and putting something like this in it:
import { Injectable, ComponentRef } from '#angular/core';
import {PopupComponent} from '../popup/popup.component';
#Injectable({
providedIn: 'root'
})
export class PopupService {
popup_ref: ComponentRef<PopupComponent>
constructor(
) { }
//Implemented in orderoverviewcomponent, invoicecomponent, and placeordercomponent
public openPopup(id1:string, storage_label1:string, id2:string, storage_label2:string): Boolean{
if (id1){
localStorage.setItem(storage_label1, JSON.stringify(id1));
}
if (id2){
localStorage.setItem(storage_label2, JSON.stringify(id2));
}
this.popup_ref.initiate(); //this line is a made up example of loading the component
return true;
}
public closePopup(storage_label1: string, storage_label2:string): Boolean{
if(storage_label1){
localStorage.removeItem(storage_label1);
}
if(storage_label2){
localStorage.removeItem(storage_label2);
}
this.popup_ref.destroy();
return false;
}
}
Where this.popup_ref.destroy(); would ideally destroy PopupComponent, but when I did that I got a "cannot read property of undefined" on the popup_ref, I'm having trouble declaring it, the syntax seems a bit tricky.
The problem also remains that i need a function to load the component, the opposite of .destroy(), if this is possible I would much prefer it over loading and destroying with *ngIf.
Edit: Partially solved it by just using a boolean in the service as the trigger for *ngIf, is there a way to do a function load and destroy on a component still?
You can bind an EventEmitter() to your component to invoke a function in the parent component.
<app-popup [onClose]="load_popup = false" *ngIf="load_popup"></app-popup>
Then inside of your app-popup component:
#Output onClose = new EventEmitter();
public closePopup(/* code emitted for brevity */) {
/* code emitted for brevity */
this.onClose.emit(); //Call the parent function (in this case: 'load_popup = false')
}
It's important to know that you can pass entire functions to the bound function, and you can even pass variables back to the parent from the child:
[onClose]="myFunction($event)"
this.onClose.emit(DATA HERE);
As an aside, since you're using Angular; I would suggest looking into using Modals for popup dialogue boxes. You can see a good example here:
https://ng-bootstrap.github.io/#/components/modal/examples

Angular 4 Hide Dropdown on certain pages

I want to hide a Dropdown (it's elements included) on a certain page. I'd do it with a '*ngIf'-request, however I'm not sure about the condition.
The router path is '/project' but I can't access it - therefore *ngIf="path==='/project'" won't work.
Any ideas on what condition should be used? Or a better solution to the problem.
There also are subpaths like /project/id, on which the Dropdown should be available.
In your component/controller, try something like:
showDropdown: boolean = false;
ngOnInit() {
this.showDropdown = path === '/project'
}
path is maybe a Input-variable of your component?
#Input
path: string;
In your HTML-Template, you do something like:
<div *ngIf="showDropdown">test</div>
In your controller, add a boolean visible ( true or false )
and in you html simply add : *ngIf="visible" to your div
As I understood your situation, you need to do the following:
In the navigation bar component you have to subscribe to routing to get the current path which will help you to realize whether show or not your dropdown, do the following
import { Router, NavigationEnd } from '#angular/router';
public showDropDown: boolean;
constructor(private router: Router) {}
...
ngOnInit() {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.showDropdown = event.url.indexOf('/project') >= 0;
}
}
}
In your html where your dropdown is present
<div class="my-dropdown" *ngIf="showDropdown">
... (your dropdown)
</div>
this will help you to avoid dependencies from other components and be able to manipulate data according to routing. Surely there will always be a way to improve this. Everything depends on what you want as a result.

Binding to Correct Service Call in My Angular 2 App

In my Angular 2 app I have several different components that display data for different groups. Each of the different groups has a different API service call. Other than the different data set, though, the tabular display/layout itself is the same for each one.
In the component for each respective group, I am using the service call like this (this one is for "group1"). I am subscribing to the data in my OnInit in my group1.component.ts:
ngOnInit() {
this.group1Service.getGroup()
.subscribe(resRecordsData => this.records = resRecordsData,
responseRecordsError => this.errorMsg = responseRecordsError);
}
Now, what I'd like to do is cut down on the duplication (i.e. make it dry-er) by abstracting out the tabular display so I can just drop that into each component view as a child view. So the view for component 1, for instance would look like this ("table-display" is the part that's abstracted out in the below code):
<div class="page-view">
<div class="page-view-left">
<comp1-left-panel></comp1-left-panel>
</div>
<div class="page-view-right">
<div class="page-content">
<table-display></table-display>
</div>
</div>
</div>
My question is, how can I bind the right service call (i.e. the right component) to the "table-display" for each component? Would I use an #Input here, or perhaps square bracket binding?
Yes, you would use an input on your table-display component and fill it from the parent component:
<table-display [Data]="arrayOfData"></table-display>
Where [Data] is define in your table-display as:
#input Data: Array<any>;
You can pass required data as Input to table-display component.
If all of the components shares same type of structure to show data in table. Then I would recommend to create a separate class for common data and pass the object of that common class.
You can write a mapper function in each component which will return the required data to table-display , alternatively if it's a simple JSON like structure then you can pass it on fly.
e.g
Let's say there are 4 attributes you need to show in table-display ,
We create common.ts
export class Common {
col1: string; #whatever data structure you need
col2: number;
col3: number[];
col4: Object;
constructure(col1: any,col2: any,col3: any,col4: any){
this.col1 = col1;
//set other attributes similarly
}
}
no in component1.component.ts
import {Common} from './path_to_common.ts'
#other imports here
#component {
selector: 'app-component1',
template: "your about template"
}
export class Component1Component implements OnInit{
common: Common; #this is the common attribute you will pass to **table-display**
constructure(component1_service: Component1Service){
}
ngOnInit(){
#get your data from service here
#now set **common** attribute here by settings 4 attributes we defined
#e.g
this.common = new Common(service_record.col1,service_record.col2....)
}
}
that's it now you can pass this common attribute as input by:
in your component templates
<table-display [common]="common"></table-display>
now write TableDisplayComponent
import {Input} from '#angular/core'
`import {Common} from './path_to_common.ts'
#other imports here
#component {
selector: 'table-display'
template: `what ever your template is to show table`
}
export class TableDisplayComponent{
#Input() common: Common; #This common will be passed as input from all of your components
}

Angular 2 how to pass function with params to a child component?

How do you properly pass a function from a parent to a child component when the function takes in parameters?
In the ngOnInit, how to scope a function like:
addToList(id) {
this.store.dispatch(this.listActions.addToList(id));
}
ngOnInit, which is wrong right now.
ngOnInit() {
this.addToList = this.addToList.bind(this, id);
}
In my parent component, I have the addToCart(id) function.
I want to pass that function to my child component, which has a list of items, and on clicking the ADD button on an item, I want to callback addToCart(item_id) to the parent.
#Maarek's answer is a good one, and is the 'right' way to do it, probably. What I am presenting here is a simpler means of communicating specifically from the Child to the Parent.
What you proposed in the original post was to have the Parent send a callback method to the Child, so the Child can call it with data when appropriate. To accomplish this specific task (data from Child to Parent on some action in the Child) using Events is appropriate, using the EventEmitter from inside the Child. See this API reference which has an example: https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html and this Plunker I made as a demo: https://embed.plnkr.co/T1wFqVOhMXgX6NRfTuiC/
In the child, you have code like this:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'item',
template: `
<div class="item">
<button type="button" (click)="addItem()">Add</button>
<p>{{id}}
</div>
`
})
export class ItemComponent {
#Input() id: string;
//key line here: this emitter can be bound to by parent to get notifications
#Output() add: EventEmitter<string> = new EventEmitter<string>();
constructor() { }
addItem() {
//then when the button is clicked, emit events to the parent.
this.add.emit(this.id);
}
}
The Parent would call create the component like this:
<item id="1" (add)="addToList($event)"></item>
Where addToList() is a function on the Parent that does the work your callback was intended to do. The $event is the data passed from the child (the id).
There's not a lot of detail here, but from what I'm gathering I think what you will want is an injectable service (demonstrated here: https://angular.io/docs/ts/latest/tutorial/toh-pt4.html) to handle the data objects being shared between the components. Rather than type a bunch of code in here (which is better shown at that page in the tutorial) I'll describe what I think you're trying to do and how I'd go about doing it.
The entire store data model can be handled via a service (store.service.ts maybe). Which will have your CRUD functions exposed for the different properties of the store model. The list you are adding to here should have a public getter that returns an observable of the list in the service as well as a public function for adding and deleting from the list. Something like this:
#Injectable
export class StoreService {
private _storeList:BehaviorSubject<Array<any>> = new BehaviorSubject<Array<any>>([]);
/*I'm sure the store has other properties, set them up here. I'd suggest
breaking any arrays out of the general object (unless you want to use
pipes which are awesome but keeping it simple here) but if the store has
a lot of general properties (name, address, whatever) they can be stored
in a single BehaviorSubject of type any.
*/
constructor(){}
get StoreList() { return this._storeList.asObservable() }
public addToList(id) {
let curVal = this._storeList.getValue();
curVal.push(id);
this._storeList.next(curVal);
}
}
You would then inject this service into the constructor of both the parent and the child constructor(private _storeService:StoreService){} (and any other components that need it). The child could then subscribe to the list: get List() { return this._storeService.StoreList } and the parent can call the add function to add to the list. One thing to note, when you add this to your template as an *ngFor, make sure to pass the value through the async pipe. *ngFor="List | async" or your may tear your hair out trying to figure out why you're getting errors.
This article helped me a lot with this as well (although I might suggest avoiding immutable at first until you're comfortable with Angular 2 completely): http://blog.angular-university.io/how-to-build-angular2-apps-using-rxjs-observable-data-services-pitfalls-to-avoid/

Categories