Como faço para compartilhar dados entre componentes no Angular 2?

84

No Angular 1.xx você simplesmente solicita o mesmo serviço e acaba com a mesma instância, possibilitando o compartilhamento dos dados no serviço.

Agora no Angular 2 tenho um componente que faz referência ao meu serviço. Posso ler e modificar os dados no serviço, o que é bom. Quando tento injetar o mesmo serviço em outro componente, parece que recebo uma nova instância.

O que estou fazendo errado? É o próprio padrão que está errado (usando um serviço para compartilhar dados) ou preciso marcar o serviço como um singleton (em uma instância do aplicativo) ou algo assim?

Estou no 2.0.0-alpha.27/ btw

Eu injeto um serviço por meio de appInjector(editar: agora providers) na @Componentanotação e, em seguida, salvo uma referência no construtor. Ele funciona localmente no componente - apenas não entre os componentes (eles não compartilham a mesma instância de serviço) como eu pensei que fariam.

ATUALIZAÇÃO : a partir do Angular 2.0.0 agora temos @ngModule onde você definiria o serviço sob a providerspropriedade em dito @ngModule. Isso garantirá que a mesma instância desse serviço seja passada para cada componente, serviço, etc. nesse módulo. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

ATUALIZAÇÃO : Muita coisa aconteceu com o desenvolvimento do Angular e do FE em geral. Como @noririco mencionou, você também pode usar um sistema de gerenciamento de estado como o NgRx: https://ngrx.io/

Per Hornshøj-Schierbeck
fonte
Seis métodos para compartilhar dados entre componentes angulares: - angulartutorial.net/2017/12/…
Prashobh
Se você chegar aqui, por favor, considere usar um sistema de gestão do ESTADO
noririco

Respostas:

63

Um singleton de serviço é uma boa solução. Outra maneira - data/events bindings.

Aqui está um exemplo de ambos:

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

Assista ao vivo

Alexander Ermolov
fonte
20
Eu descobri. Você injeta apenas uma instância de serviço - em 'app'. Essa mesma instância é herdada automaticamente ao adicionar o parâmetro aos construtores filhos :) Eu cometi o erro de adicionar outro appInjector aos componentes filhos que cria novas instâncias.
Per Hornshøj-Schierbeck
1
@AlexanderCrush você poderia atualizar sua resposta? Como nas versões alfa posteriores (alfa 30+), o appInjector foi removido . A resposta correta, por enquanto, deve ser usar viewInjector.
Eric Martinez
1
@EricMartinez obrigado, resposta e plunker foram atualizados.
Alexander Ermolov
1
Recurso interessante para entender por que e como isso funciona: blog.thoughtram.io/angular/2015/08/20/… .
soyuka de
2
Eu estava tendo o mesmo problema, pois estava injetando o Service no app principal, e também no próprio componente usando providers: [MyService]. Removendo os provedores, tornou-se a única instância do aplicativo
maufarinelli
43

O comentário de @maufarinelli merece sua própria resposta porque até que eu vi, eu ainda estava batendo minha cabeça contra a parede com esse problema, mesmo com a resposta de @Alexander Ermolov.

O problema é que, quando você adiciona um providersao seu component:

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

Isso faz com que uma nova instância de seu serviço seja injetada ... em vez de ser um singleton .

Portanto, remova todas as instâncias do seu providers: [MyService]em seu aplicativo, exceto no module, e funcionará!

Serj Sagan
fonte
2
Apenas um comentário, nunca é um singleton - é apenas a mesma instância sendo passada adiante. Você ainda pode solicitar uma nova instância ...
Por Hornshøj-Schierbeck
10

Você deve usar entradas e saídas de um decorador @Component. Aqui está o exemplo mais básico do uso de ambos;

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);
Jan Kuri
fonte
7
Não há necessidade de importar ngFor,
Richard Hamilton
7

No modelo de componente pai:

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

No componente filho:

@Input() hero: Hero;

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

Alexis Gamarra
fonte
Pode ser, mas seriam necessários mais detalhes. No mundo real não é tão fácil. imagine que você tem uma classe que deseja compartilhar entre vários componentes e acessar dados. isso não funciona.
sancelot
Estou usando essa abordagem em uma grande solução para compartilhar dados entre muitos componentes. Você pode ter muitos filhos e cada um recebendo o mesmo objeto. Você já tentou fazer isso antes de dizer que não funciona?
Alexis Gamarra
Sim eu fiz . vai funcionar .... mas com alguns "hackeamentos" para resolver alguns problemas. Sua resposta não permite que ninguém o use.
sancelot
2

Existem muitos caminhos. Este é um exemplo de propagação entre elementos pais e filhos. Isso é muito eficiente.

Enviei um exemplo que permite ver o uso de ligação de dados de duas maneiras em dois formulários. Se alguém puder fornecer uma amostra plunkr, isso seria muito bom ;-)

Você pode procurar outra maneira usando um provedor de serviços. Você pode dar uma olhada neste vídeo também para referência: ( Compartilhando dados entre componentes em Angular )

mymodel.ts (dados para compartilhar)

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

Lembre-se: deve haver um pai que irá compartilhar "mymodel" com os componentes filhos.

Componente pai

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() {
    }
}

Componente filho, 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() {
    }
}

Nota: Em alguns casos raros, pode ocorrer um erro quando o código HTML é analisado, porque o modelo não está "pronto" para ser usado na inicialização da página. Nesse caso, prefixe o código HTML com uma condição ngIf:

<div *ngIf="model"> {{model.data1}} </div>
Sancelot
fonte
1

Depende, se houver um caso simples

a) A -> B -> C A tem dois filhos B e C e se você quiser compartilhar dados entre A e B ou A e C, use (entrada / saída)

Se você deseja compartilhar entre B e C, você também pode usar (entrada / saída), mas sugere-se usar o serviço.

b) Se a árvore for grande e complexa. (se houver tantos níveis de conexões de pais e filhos.) E, neste caso, se você quiser compartilhar dados, eu sugeriria ngrx

Ele implementa a arquitetura de fluxo que cria uma loja do lado do cliente para a qual qualquer componente pode se inscrever e pode atualizar sem criar qualquer condição de corrida.

Pratiyush
fonte