I am passing products from a parent component to a child component using input binding and it works the first time the child component is invoked. I expect change to products in filterProduct() to be reflected in the child every time it is modified in the parent but this is not happening. How do I get this to happen.
Parent template:
<div class="container-fluid">
<div class="col-sm-9">
<app-product-card [products]=products></app-product-card>
</div>
Parent component:
@Component({
selector: 'app-appliances-product-card',
moduleId: module.id,
templateUrl: 'appliances-product-card.component.html'
})
export class AppliancesProductCardComponent implements OnInit{
products: Product[];
filterProduct(filter) {
this.products = this.filteredProducts;
}
}
Child component:
@Component({
selector: 'app-product-card',
moduleId: module.id,
templateUrl: 'product-card.component.html'
})
export class ProductCardComponent {
@Input() products: Product[];
}
To let Angular know that a property in a child component or directive can receive its value from its parent component we must use the @Input() decorator in the said child. The @Input() decorator allows data to be input into the child component from a parent component.
@Input Decorator: This is used to define an input property and it is used to send data from parent component to child component. @Output Decorator: This is used to bind a property of the type of Angular EventEmitter class and it is used to pass data from child component to parent component.
@Input() and @Output() give a child component a way to communicate with its parent component. @Input() lets a parent component update data in the child component. Conversely, @Output() lets the child send data to a parent component.
Pass data from parent to child component using @Input() decorator, which allows data to pass through templates and child to parent component using @Output() decorator with the help of Event Emitter. Create a new Angular project using the following NPM command: ng new componentSharing.
Here's plunker of what might work for you:
https://plnkr.co/edit/S47fBh2xdT1gp2zrznk4?p=preview
Instead of passing the data from parent to child component, both parent and child component will use service to get the data.
Parent Component:
import { Component, OnInit } from '@angular/core';
import { Product } from './product'
import { ProductsService } from './products.service';
@Component({
selector: 'app-appliances-product-card',
template: `
<div class="container-fluid">
<button (click)="filterProduct('Type 1')">Filter Type 1</button>
<button (click)="filterProduct('')">Clear Filter</button>
<div class="col-sm-9">
<app-products-card></app-products-card>
</div>
</div>
`
})
export class AppliancesProductCardComponent implements OnInit {
products: Product[];
constructor(private _productsService: ProductsService){ }
filterProduct(type: string) {
this.products = this._productsService.filterProduct(type);
}
ngOnInit() {
this.products = this._productsService.getAllProducts();
}
}
Child Component:
import { Component, OnInit, Input } from '@angular/core';
import { Product } from './product';
import { ProductsService } from './products.service';
@Component({
selector: 'app-products-card',
template: `
<h1>Product Card</h1>
<div *ngFor="let product of products">{{product.name}} - {{product.type}}</div>
`
})
export class ProductsCardComponent implements OnInit {
constructor(private _productsService: ProductsService){ }
ngOnInit() {
this.products = this._productsService.getAllProducts();
this._productsService.filteredProducts.subscribe(products => {
console.log(products);
this.products = products
});
}
}
Service:
import { EventEmitter, Injectable } from '@angular/core';
import { Product } from './product';
export class ProductsService {
filteredProducts = new EventEmitter<Product[]>;
private products: Product[] = [
{ id: 1, name: 'Product 1', price: 10.50, type: 'Type 1' },
{ id: 2, name: 'Product 2', price: 15.50, type: 'Type 1' },
{ id: 3, name: 'Product 3', price: 1.50, type: 'Type 2' },
{ id: 4, name: 'Product 4', price: 100.50, type: 'Type 3' }
];
getAllProducts(): Product[] {
return this.products.slice();
}
filterProduct(type: string): Product[]{
let filteredProducts = this.products.filter(p => !type || p.type == type).slice();
this.filteredProducts.emit(filteredProducts);
}
}
I came across similar problems when I first started writing angular application. Passing data from parent to child component all the time is not a manageable way to write application. Specially if it's a big application with too many nested components.
In this example, I'm using the services and event emitter (both part of angular framework) to provide relevant data to both the child and the parent component.
One way to improve the code is to use rxjs. This is what angular event emitter uses behind the scene.
And even more advanced solution is to use ngrx/store (redux library for Angular). This is particularly suited for huge applications.
No matter which solution you choose, the idea is to have a single place to maintain the state of the application and avoid nesting data too much.
If you'd like for the child component to react to changes in the parent component you'll need to implement ngOnChanges()
with SimpleChange
from @angular/core
. Detailed documentation can be found on the Angular - linkComponent Interaction Page, but the main idea is expressed in two chunks of code:
Child Component
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
@Component({
selector: 'version-child',
template: `
<h3>Version {{major}}.{{minor}}</h3>
<h4>Change log:</h4>
<ul>
<li *ngFor="let change of changeLog">{{change}}</li>
</ul>
`
})
export class VersionChildComponent implements OnChanges {
@Input() major: number;
@Input() minor: number;
changeLog: string[] = [];
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
let log: string[] = [];
for (let propName in changes) {
let changedProp = changes[propName];
let to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to ${to}`);
} else {
let from = JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to ${to}`);
}
}
this.changeLog.push(log.join(', '));
}
}
Parent Component
import { Component } from '@angular/core';
@Component({
selector: 'version-parent',
template: `
<h2>Source code version</h2>
<button (click)="newMinor()">New minor version</button>
<button (click)="newMajor()">New major version</button>
<version-child [major]="major" [minor]="minor"></version-child>
`
})
export class VersionParentComponent {
major = 1;
minor = 23;
newMinor() {
this.minor++;
}
newMajor() {
this.major++;
this.minor = 0;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With