简体   繁体   中英

Angular 2 Passing data from Parent component to child

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[];
}

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;
  }
}

You could leverage shared service for this. It could contain both data and observables to subscribe on to be notified when data are updated.

Service

export class ListService {
  list1Event: EventEmitter<any> = new EventEmitter();

  getLists() {
    return this.http.get(url).map(res => res.json())
      .subscribe(
        (data) => {
          this.list1Event.emit(data.list1);
        }
      );
  }
}

Component

@Component({
  selector: 'my-component1',
  template: `
    <ul>
     <li *ngFor="#item of list">{{item.name}}</li>
    </ul>
  `
})
export class MyComponent1 {
  constructor(private service:ListService) {
    this.service.list1Event.subscribe(data => {
      this.list = data;
    });
  }
}

SOLUTION USED: Declared a variable using the @ViewChild decorator in the parent component that points to property in the child component.

@Component({ selector: 'app-appliances-product-card', moduleId: module.id, templateUrl: 'appliances-product-card.component.html' })

export class AppliancesProductCardComponent implements OnInit{    
   products: Product[];

@ViewChild(ProductCardComponent )
private productCardComponent : ProductCardComponent;

   filterProduct(filter) {
     this.products = productCardComponent.product;
  }
}

There are a couple of way to pass the data from parent component to child component.

Here you go.

How parent component pass the data to child component ?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM