Monkey D. Luffy
Monkey D. Luffy

Reputation: 321

Parent / Child component communication angular 2

I am failing to implement action button in child_1 component but the event handler is in sub child component child_2 as shown in the following code:

app.component.html (Parent Html)

<div style="text-align:center">
  <h1>
    Welcome to {{title}}!
  </h1>
  <app-navigation></app-navigation> <!-- Child1-->
</div>

app.component.html (Parent Component)

import { Component } from '@angular/core';
import { ProductService } from './productservice';
import {Product} from './product';

@Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      title = 'MobileShirtShoeApp';
    }

app.module.ts (Main Module)

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { Product } from './product';
import { ProductService } from './productservice';
import { AppComponent } from './app.component';
import { NavigationComponent } from './navigation/navigation.component';
import { DataTemplateComponent } from './data-template/data-template.component';

@NgModule({
  declarations: [AppComponent,NavigationComponent,DataTemplateComponent],
  imports: [BrowserModule,HttpModule],
  providers: [ProductService],
  bootstrap: [AppComponent]
})

export class AppModule { }

navigation.component.html (Child 1 HTML)

<fieldset>
  <legend>Navigate</legend>
  <div>
     <button  (click)="loadMobiles()">Mobiles</button> <!--Child_1 Action-->
  </div>
  <app-data-template></app-data-template>
</fieldset>

navigation.component.ts (Child 1 Component.ts)

import { Component, OnInit } from '@angular/core';
import { ProductService } from '../productservice';
import {Product} from '../product';
import {DataTemplateComponent} from '../data-template/data-template.component';


@Component({
  selector: 'app-navigation',
  templateUrl: './navigation.component.html',
  styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
  error: string;
  productArray: Product[];

  constructor(private myService: ProductService){
    this.myService = myService;
  }

  dataTemplateComponent: DataTemplateComponent = new DataTemplateComponent(this.myService);

  ngOnInit() {
  }

  loadMobiles() {
   return this.dataTemplateComponent.loadMobiles();
  }
}

data-template.component.html (Child 2 HTML) (NOT DISPLAYING DATA)

<fieldset>
    <legend>Requested Data</legend>
    Welcome
    <div>
      <ul>
            <li *ngFor="let product of productArray">
                {{product.id}} {{product.name}} {{product.price}}
                <img src="{{product.url}}">
            </li>
        </ul>
    </div>
</fieldset>

data-template.component.ts (Child 2 Component) (Contains Product service calling code)

import { Component} from '@angular/core';
import {Product} from '../product';
import {ProductService} from '../productservice';

@Component({
  selector: 'app-data-template',
  templateUrl: './data-template.component.html',
  styleUrls: ['./data-template.component.css']
})

export class DataTemplateComponent  {
  error: string;
  productArray: Product[];
  constructor(private productService: ProductService) {
    this.productService = productService;
  }

  loadMobiles(){
    let promise  = this.productService.fetchMobiles();
    promise.then(productArr => {
        return this.productArray = productArr;
      }).catch((err) => {
       this.error = err;
      });
  }
}

ProductService.ts

import 'rxjs/add/operator/toPromise';
import {Http, HttpModule} from '@angular/http';
import {Injectable} from '@angular/core';
import {Product} from './product';


@Injectable()
export class ProductService{
  http: Http;
  constructor(http: Http){
  this.http = http;
  console.log(http);
 }

fetchMobiles(): Promise<Product[]>{
  let url = "https://raw.githubusercontent.com/xxxxx/Other/master/JsonData/MobileData.json";
  return this.http.get(url).toPromise().then((response) => {
        return response.json().mobiles as Product[];
    }).catch(this.handleError);
}

private handleError(error: any): Promise<any> {
  console.error('An error occurred', error);
  return Promise.reject(error.message || error);
}
}

Sorry if the code bothers you. So basically i am failing to display service data in child_2.html when an action made in child_1.html.The service working fine and name is ProductService which uses Product.ts as an object to get the data in JSON format. Any kind of help is appreciated.

Upvotes: 2

Views: 266

Answers (1)

bryan60
bryan60

Reputation: 29305

This doesn't work because the DataTemplateComponent you're instantiating in app-navigation isn't the same instance of DataTemplateComponent as the one on the page. It's a brand new one that you instantiated and that isn't bound to the page at all. What you're trying to achieve is component communication. Specifically, parent / child component communication. There are a number of ways to do this, the cleanest and most flexible / extensible way is with a shared service pattern. Basically, you declare a service with an observable in it that you inject into both services and one updates the observable while the other is subscribed to it, like this:

@Inject()
export class MyComponentCommunicationService {
    private commSubject: Subject<any> = new Subject();
    comm$: Observable<any> = this.commSubject.asObservable();
    notify() {
        this.commSubject.next();
    }
}

Then provide this service, either at the app module or possibly at the parent component depending on needs then in app navigation:

constructor(private commService: MyComponentCommunicationService) {}
loadMobiles() {
   this.commservice.notify();
}

and in data template:

constructor(private commService: MyComponentCommunicationService, private productService: ProductService) {}
ngOnInit() {
    this.commSub = this.commService.comm$.subscribe(e => this.loadMobiles());
}
ngOnDestroy() { this.commSub.unsubscribe(); } // always clean subscriptions

This is probably a little unneccessary since you already have the product service there. You could probably just move the load mobiles logic into the product service and have that trigger an observable that the data template service is subscribed to, and have the nav component call the load mobile method on the product service, but this is just meant to illustrate the concept.

I'd probably do it like this:

@Inject()
export class ProductService {
    private productSubject: Subject<Product[]> = new Subject<Product[]>();
    products$: Observable<Product[]> = this.productSubject.asObservable();
    loadMobiles() {
        this.fetchMobiles().then(productArr => {
            this.productSubject.next(productArr);
          }).catch((err) => {
            this.productSubject.error(err);
          });
    }
}

then nav component:

loadMobiles() {
   this.myService.loadMobiles();
}

then data template:

ngOnInit() {
    this.productSub = this.productService.products$.subscribe(
        products => this.productArray = products,
        err => this.error = err
    );
}
ngOnDestroy() { this.productSub.unsubscribe(); } // always clean subscriptions

Upvotes: 2

Related Questions