How do I share data between components in Angular 2?
Asked Answered
T

6

91

In Angular 1.x.x you simply ask for the same service and you end up with the same instance, making it possible to share the data in the service.

Now in Angular 2 I have a component that has a reference to my service. I can read and modify the data in the service, which is good. When I try to inject the same service in another component, it seems as if I get a new instance.

What am I doing wrong? Is it the pattern itself that is wrong (using a service to share data) or do I need to mark the service as a singleton (within one instance of the app) or something?

I'm on 2.0.0-alpha.27/ btw

I inject a service through appInjector (edit: now providers) in the @Component annotation and then save a reference in the constructor. It works locally in the component - just not across components (they do not share the same service instance) like I thought they would.

UPDATE: As of Angular 2.0.0 we now have @ngModule where you would define the service under the providers property on said @ngModule. That will ensure the same instance of that service to be passed to each component, service, etc. in that module. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

UPDATE: A lot has happened to Angular and FE development in general. As @noririco mentioned, you could also use a state management system like NgRx: https://ngrx.io/

Tildi answered 24/6, 2015 at 12:41 Comment(1)
If you get here please consider using a STATE Management systemPatroclus
I
64

A service singleton is a nice solution. Other way - data/events bindings.

Here is an example of both:

class BazService{
  n: number = 0;
  inc(){
    this.n++;
  }
}

@Component({
  selector: 'foo'
})
@View({
  template: `<button (click)="foobaz.inc()">Foo {{ foobaz.n }}</button>`
})
class FooComponent{
  constructor(foobaz: BazService){
    this.foobaz = foobaz;
  }
}

@Component({
  selector: 'bar',
  properties: ['prop']
})
@View({
  template: `<button (click)="barbaz.inc()">Bar {{ barbaz.n }}, Foo {{ prop.foobaz.n }}</button>`
})
class BarComponent{
  constructor(barbaz: BazService){
    this.barbaz = barbaz;
  }
}

@Component({
    selector: 'app',
    viewInjector: [BazService]
})
@View({
  template: `
    <foo #f></foo>
    <bar [prop]="f"></bar>
  `,
  directives: [FooComponent, BarComponent]
})
class AppComponent{}

bootstrap(AppComponent);

Watch live

Inna answered 24/6, 2015 at 21:14 Comment(9)
It works. I have to take a look at my own code again to figure out why that doesn't work. Somehow my code behaves as if each component (in your example foo and bar) each got its own instance of the service...Skees
I figured it out. You only inject one service instance - in 'app'. That same instance is inherited automaticly when adding the parameter to the child constructors :) I did the mistake of adding another appInjector to the child components which creates new instances.Skees
@AlexanderCrush could you update your answer? Since in later alpha versions (alpha 30+) appInjector was removed. The correct answer, for now, should be to use viewInjector.Dogma
@EricMartinez thanks, answer and plunker have been updated.Inna
Interesting resource to understand why and how this works: blog.thoughtram.io/angular/2015/08/20/….Whistle
I was having the same issue, because I was injecting the Service in the main app, and also in the component itself using providers: [MyService]. Removing the providers, it became the only instance of the appZambia
Another useful resource: groups.google.com/forum/#!topic/angular/Sh-iKbW4RWsEndres
is there a way to use same Service Object in Parent and Child Component to implement data sharing...Dibble
@AkshayKhale Be sure that the service appear only once in the provider list: only in parent, not in child. Or if you can try data binding where possible.Teryn
C
43

The comment by @maufarinelli deserves its own answer because until I saw it, I was still bashing my head against the wall with this issue even with @Alexander Ermolov's answer.

The problem is that when you add a providers to your component:

@Component({
    selector: 'my-selector',
    providers: [MyService],
    template: `<div>stuff</div>`
})

This causes a new instance of your service to be injected... rather than being a singleton.

So remove all instances of your providers: [MyService] in your application except in the module, and it will work!

Cuneal answered 22/10, 2016 at 10:54 Comment(1)
Just a comment, it's never a singleton - it's just the same instance being passed around. You could still request a new instance...Skees
Z
10

You must use inputs and outputs of a @Component decorator. Here is the most basic example of using both;

import { bootstrap } from 'angular2/platform/browser';
import { Component, EventEmitter } from 'angular2/core';
import { NgFor } from 'angular2/common';

@Component({
  selector: 'sub-component',
  inputs: ['items'],
  outputs: ['onItemSelected'],
  directives: [NgFor],
  template: `
    <div class="item" *ngFor="#item of items; #i = index">
      <span>{{ item }}</span>
      <button type="button" (click)="select(i)">Select</button>
    </div>
  `
})

class SubComponent {
  onItemSelected: EventEmitter<string>;
  items: string[];

  constructor() {
    this.onItemSelected = new EventEmitter();
  }

  select(i) {
    this.onItemSelected.emit(this.items[i]);
  }
}

@Component({
  selector: 'app',
  directives: [SubComponent],
  template: `
    <div>
      <sub-component [items]="items" (onItemSelected)="itemSelected($event)">
      </sub-component>
    </div>
  `
})

class App {
  items: string[];

  constructor() {
    this.items = ['item1', 'item2', 'item3'];
  }

  itemSelected(item: string): void {
    console.log('Selected item:', item);
  }
}

bootstrap(App);
Zamia answered 26/12, 2015 at 2:38 Comment(1)
There's no need to import ngFor,Tribadism
S
7

In parent Component template:

<hero-child [hero]="hero">
</hero-child>

In child Component:

@Input() hero: Hero;

Source: https://angular.io/docs/ts/latest/cookbook/component-communication.html

Shandashandee answered 25/3, 2017 at 23:56 Comment(3)
May be, but this would need more details. In the real world it is not so easy. imagine you have a class you want to share between multiple components, and access data . that does not work.Parhe
I'm using this approach in a big solution to share data between many components. You can have many childs and each one receiving the same object. Did you try to do this before say that it doesn't work?Shandashandee
Yes, I did . it will work .... but with some "hackings" in order to solve some issues . Your answer does not permit anybody to use it.Parhe
P
2

There are many ways. This one is an example using propagation between parent and child elements. This is very efficient.

I submitted an example that permits to view the usage of two ways databinding within two forms. If somebody can provide a plunkr sample this would be very nice ;-)

You may look for another way using a service provider. You may have a look at this video too for reference: (Sharing Data between Components in Angular)

mymodel.ts (data to share)

// Some data we want to share against multiple components ...
export class mymodel {
    public data1: number;
    public data2: number;
    constructor(
    ) {
        this.data1 = 8;
        this.data2 = 45;
    }
}

Remember: There must be a parent that will share "mymodel" to child components.

Parent component

import { Component, OnInit } from '@angular/core';
import { mymodel } from './mymodel';
@Component({
    selector: 'app-view',
    template: '<!-- [model]="model" indicates you share model to the child component -->
        <app-mychild [model]="model" >
        </app-mychild>'

        <!-- I add another form component in my view,
         you will see two ways databinding is working :-) -->
        <app-mychild [model]="model" >
        </app-mychild>',
})

export class MainComponent implements OnInit {
    public model: mymodel;
    constructor() {
        this.model = new mymodel();
    }
    ngOnInit() {
    }
}

Child component, mychild.component.ts

import { Component, OnInit,Input } from '@angular/core';
import { FormsModule }   from '@angular/forms'; // <-- NgModel lives here
import { mymodel } from './mymodel';

@Component({
    selector: 'app-mychild',
    template: '
        <form #myForm="ngForm">
            <label>data1</label>
            <input type="number"  class="form-control" required id="data1 [(ngModel)]="model.data1" name="data1">
            <label>val {{model.data1}}</label>

            label>data2</label>
            <input  id="data2"  class="form-control" required [(ngModel)]="model.data2" name="data2" #data2="ngModel">
            <div [hidden]="data2.valid || data2.pristine"
                class="alert alert-danger">
                data2 is required
            </div>

            <label>val2 {{model.data2}}</label>
        </form>
    ',
})

export class MychildComponent implements OnInit {
    @Input() model: mymodel ;  // Here keywork @Input() is very important it indicates that model is an input for child component
    constructor() {
    }
    ngOnInit() {
    }
}

Note: In some rare cases, you may have error when the HTML code is parsed, because the model is not "ready" to use at the initialisation of the page. In this case, prefix the HTML code with an ngIf condition:

<div *ngIf="model"> {{model.data1}} </div>
Parhe answered 24/7, 2017 at 7:26 Comment(0)
I
1

It depends, if there is a simple case

a) A -> B -> C A has two child B and C and if you want to share data between A and B or A and C then use (input / output)

If you want to share between B and C then also you can use (input / output) but it is suggested to use Service.

b) If the tree is big and complex. (if there are so many levels of parent and children connections.) And in this case if you want to share data then I would suggest ngrx

It implements the flux architecture which creates a client side store to which any component can subscribe and can update without creating any race condition.

Ipecac answered 20/5, 2019 at 10:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.