47

I have a questions about passing data in Angular.

First, I don't have a structure as <parent><child [data]=parent.data></child></parent>

My structure is

<container>
  <navbar>
    <summary></summary>
    <child-summary><child-summary>
  </navbar>
  <content></content>
</container>

So, in <summary /> I have a select that do send value to <child-summary /> and <content />.

OnSelect method is well fired with (change) inside <summary /> component.

So, I tried with @Input, @Output and @EventEmitter directives, but I don't see how retrieve the event as @Input of the component, unless to go on parent/child pattern. All examples I've founded has a relation between component.

EDIT : Example with BehaviorSubject not working (all connected service to API works well, only observable is fired at start but not when select has value changed)

shared service = company.service.ts (used to retrieve company data)

import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class SrvCompany {

    private accountsNumber = new BehaviorSubject<string[]>([]);
    currentAccountsNumber = this.accountsNumber.asObservable();

    changeMessage(accountsNumber: string[]) {
        this.accountsNumber.next(accountsNumber);
    }

    private _companyUrl = 'api/tiers/';

    constructor(private http: Http) { }

    getSociete(): Promise<Response> {
        let url = this._companyUrl;
        return this.http.get(url).toPromise();
    }
}

invoice.component.ts (the "child")

import { Component, OnInit, Input } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';

import { SrvInvoice } from './invoice.service';
import { SrvCompany } from '../company/company.service';

@Component({
    selector: 'invoice',
    templateUrl: 'tsScripts/invoice/invoice.html',
    providers: [SrvInvoice, SrvCompany]
})

export class InvoiceComponent implements OnInit  {

    invoice: any;

    constructor(private srvInvoice: SrvInvoice, private srvCompany: SrvCompany)
    {

    }

    ngOnInit(): void {
        //this.getInvoice("F001");

        // Invoice data is linked to accounts number from company.
        this.srvCompany.currentAccountsNumber.subscribe(accountsNumber => {
            console.log(accountsNumber);
            if (accountsNumber.length > 0) {
                this.srvInvoice.getInvoice(accountsNumber).then(data => this.invoice = data.json());
            }
        });
    }

    //getInvoice(id: any) {
    //    this.srvInvoice.getInvoice(id).then(data => this.invoice = data.json());
    //}
}

company.component.ts (the trigerring "parent")

import { Component, Inject, OnInit, Input } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';

import { SrvCompany } from './company.service';

@Component({
    selector: 'company',
    templateUrl: 'tsScripts/company/company.html',
    providers: [SrvCompany]    
})

export class CompanyComponent implements OnInit {

    societes: any[];    
    soc: Response[]; // debug purpose
    selectedSociete: any;

    ville: any;
    ref: any;
    cp: any;
    accountNumber: any[];

    constructor(private srvSociete: SrvCompany)
    {

    }

    ngOnInit(): void {
        this.getSocietes();
    }

    getSocietes(): void {

        this.srvSociete.getSociete()
            .then(data => this.societes = data.json())
            .then(data => this.selectItem(this.societes[0].Id));
    }

    selectItem(value: any) {
        this.selectedSociete = this.societes.filter((item: any) => item.Id === value)[0];
        this.cp = this.selectedSociete.CodePostal;
        this.ville = this.selectedSociete.Ville;
        this.ref = this.selectedSociete.Id;
        this.accountNumber = this.selectedSociete.Accounts;
        console.log(this.accountNumber);
        this.srvSociete.changeMessage(this.accountNumber);
    }
}
User.Anonymous
  • 1,719
  • 1
  • 28
  • 51

5 Answers5

98

This is a case where you want to use a shared service, as your components are structured as siblings and grandchildren. Here's an example from a video I created a video about sharing data between components that solves this exact problem.

Start by creating a BehaviorSubject in the service

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class DataService {

  private messageSource = new BehaviorSubject("default message");
  currentMessage = this.messageSource.asObservable();

  constructor() { }

  changeMessage(message: string) {
    this.messageSource.next(message)
  }

}

Then inject this service into each component and subscribe to the observable.

import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";
@Component({
  selector: 'app-parent',
  template: `
    {{message}}
  `,
  styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {

  message:string;

  constructor(private data: DataService) { }

  ngOnInit() {
    this.data.currentMessage.subscribe(message => this.message = message)
  }

}

You can change the value from either component and the value will be updated, even if you don't have the parent/child relationship.

import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";
@Component({
  selector: 'app-sibling',
  template: `
    {{message}}
    <button (click)="newMessage()">New Message</button>
  `,
  styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {

  message:string;

  constructor(private data: DataService) { }

  ngOnInit() {
    this.data.currentMessage.subscribe(message => this.message = message)
  }

  newMessage() {
    this.data.changeMessage("Hello from Sibling")
  }

}
JeffD23
  • 8,318
  • 2
  • 32
  • 41
  • Are you getting the expected value when you console log from selectItem? It looks like you have the subscribe pattern correct. – JeffD23 Jun 07 '17 at 17:03
  • yes, I have an array of 2 object as expected. And in doubt, I have too hard coded the array in changeMessage in company.component. But same, I go well in changeMessage but nothing is triggered next. – User.Anonymous Jun 07 '17 at 17:15
  • I've tried with rxjs/Subject instead rxjs/BehaviorSubject as angular's doc example but it's worst (nothing is fired). – User.Anonymous Jun 07 '17 at 17:19
  • 2
    Ok, after many research, I have declared SrvCompany as provider twice and so I don't have a singleton of this... Thanks for your help. – User.Anonymous Jun 07 '17 at 17:52
  • Good catch, I didn't think of that. Glad it helped. – JeffD23 Jun 07 '17 at 18:05
  • 2
    I went with this example, with data service defined in providers in both components and it didn't also work. However, when I removed service from providers in components and put them inside main module providers everything worked accordingly. Hope it helps! – chassis Nov 21 '17 at 13:40
  • @JeffD23 So i didn't use rxjs but i created a common service and its an empty array from my components i will push data to the array.But on reload my service value gets resets ? So if i use rxjs will there be any chance that service value stays in memory since i will be subscribed to it ? I have my simple app running on this url : https://stackblitz.com/edit/data-sharing-service – Melvin Jan 23 '18 at 10:32
  • I believe I followed your instructions, but when the child is opened it displays 'default message'. Is the problem that the parent is not inserting the message into the service. Please forgive me, I am a total newbie. – Brian Fleming Apr 12 '18 at 16:38
0

if component are not related than you need use Service

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

CharanRoot
  • 6,181
  • 2
  • 27
  • 45
0

There are two solutions for this.

  1. This can be done through shared service by using observable's.

  2. You can use ngrx/store for this. This is similar to Redux arch. You will be getting data from state.

Praneeth Reddy
  • 424
  • 3
  • 7
0

Here is the simplest example of sharing data between two independent components, using event emitter and service

https://stackoverflow.com/a/44858648/8300620

Rohit Parte
  • 3,365
  • 26
  • 26
0

When you mention non related components, I'm gonna assume that they don't have any parent component. If assumption isn't correct, feel free to read another of my answers where both cases are addressed.

So, as there's no common parent, we can use an injectable service. In this case, simply inject the service in the components and subscribe to its events.

(Just like the next image shows - taken from here - except that we'll inject the service in two Components)

Inject Service in Component Angular

The documentation explains it quite well how to Create and register an injectable service.

Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145