Come condivido i dati tra i componenti in Angular 2?


84

In Angular 1.xx chiedi semplicemente lo stesso servizio e ti ritrovi con la stessa istanza, rendendo possibile la condivisione dei dati nel servizio.

Ora in Angular 2 ho un componente che ha un riferimento al mio servizio. Posso leggere e modificare i dati nel servizio, il che è positivo. Quando provo a iniettare lo stesso servizio in un altro componente, sembra che ottenga una nuova istanza.

Che cosa sto facendo di sbagliato? È lo schema stesso che è sbagliato (utilizzando un servizio per condividere dati) o devo contrassegnare il servizio come singleton (all'interno di un'istanza dell'app) o qualcosa del genere?

Sono su 2.0.0-alpha.27/ btw

Inserisco un servizio tramite appInjector(modifica: ora providers) nell'annotazione @Componente quindi salvo un riferimento nel costruttore. Funziona localmente nel componente, ma non tra i componenti (non condividono la stessa istanza di servizio) come pensavo.

AGGIORNAMENTO : A partire da Angular 2.0.0 ora abbiamo @ngModule in cui definire il servizio sotto la providersproprietà su detto @ngModule. Ciò garantirà che la stessa istanza di quel servizio venga passata a ciascun componente, servizio, ecc. In quel modulo. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

AGGIORNAMENTO : Sono successe molte cose allo sviluppo di Angular e FE in generale. Come menzionato da @noririco, potresti anche utilizzare un sistema di gestione dello stato come NgRx: https://ngrx.io/


Sei metodi per condividere i dati tra i componenti angolari: - angulartutorial.net/2017/12/…
Prashobh

Se arrivi qui, considera l'utilizzo di un sistema di gestione STATE
noririco

Risposte:


63

Un servizio singleton è una bella soluzione. Altro modo - data/events bindings.

Ecco un esempio di entrambi:

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

Guarda in diretta


20
L'avevo capito. Inietti solo un'istanza di servizio - in "app". La stessa istanza viene ereditata automaticamente quando si aggiunge il parametro ai costruttori figli :) Ho commesso l'errore di aggiungere un altro appInjector ai componenti figlio che crea nuove istanze.
Per Hornshøj-Schierbeck

1
@AlexanderCrush potresti aggiornare la tua risposta? Poiché nelle versioni successive alpha (alpha 30+) appInjector è stato rimosso . La risposta corretta, per ora, dovrebbe essere quella di usare viewInjector.
Eric Martinez

1
@EricMartinez grazie, risposta e plunker sono stati aggiornati.
Alexander Ermolov

1
Interessante risorsa per capire perché e come funziona: blog . Thoughtram.io/angular/2015/08/20/… .
soyuka

2
Avevo lo stesso problema, perché stavo iniettando il servizio nell'app principale e anche nel componente stesso utilizzando providers: [MyService]. Rimuovendo i provider, è diventata l'unica istanza dell'app
maufarinelli

43

Il commento di @maufarinelli merita una sua risposta perché fino a quando non l'ho visto, stavo ancora sbattendo la testa contro il muro con questo problema anche con la risposta di @Alexander Ermolov.

Il problema è che quando aggiungi un providersal tuo component:

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

In questo modo viene iniettata una nuova istanza del servizio ... anziché essere un singleton .

Quindi rimuovi tutte le istanze della tua providers: [MyService]applicazione tranne in module, e funzionerà!


2
Solo un commento, non è mai un singleton, è solo la stessa istanza che viene passata. Potresti ancora richiedere una nuova istanza ...
Per Hornshøj-Schierbeck

10

È necessario utilizzare input e output di un decoratore @Component. Ecco l'esempio più semplice di utilizzo di entrambi;

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

7
Non è necessario importare ngFor,
Richard Hamilton,

7

Nel modello del componente principale:

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

Nel componente figlio:

@Input() hero: Hero;

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


Può essere, ma questo richiederebbe maggiori dettagli. Nel mondo reale non è così facile. immagina di avere una classe che desideri condividere tra più componenti e accedi ai dati. quello non funziona.
sancelot

Sto usando questo approccio in una grande soluzione per condividere i dati tra molti componenti. Puoi avere molti figli e ognuno riceve lo stesso oggetto. Hai provato a farlo prima di dire che non funziona?
Alexis Gamarra

Si l'ho fatto . funzionerà .... ma con alcuni "hack" per risolvere alcuni problemi. La tua risposta non permette a nessuno di usarla.
sancelot

2

Ci sono molti modi. Questo è un esempio che utilizza la propagazione tra elementi padre e figlio. Questo è molto efficiente.

Ho presentato un esempio che consente di visualizzare l'utilizzo di due modalità di associazione di dati all'interno di due forme. Se qualcuno può fornire un campione plunkr questo sarebbe molto carino ;-)

Potresti cercare un altro modo utilizzando un fornitore di servizi. Potresti dare un'occhiata anche a questo video come riferimento: ( Condivisione dei dati tra i componenti in Angular )

mymodel.ts (dati da condividere)

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

Ricorda: deve esserci un genitore che condividerà "mymodel" con i componenti figlio.

Componente genitore

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 figlio, 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: in alcuni rari casi, potresti avere un errore durante l'analisi del codice HTML, perché il modello non è "pronto" per essere utilizzato durante l'inizializzazione della pagina. In questo caso, anteponi al codice HTML una condizione ngIf:

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

1

Dipende, se esiste un caso semplice

a) A -> B -> C A ha due figli B e C e se vuoi condividere i dati tra A e B o A e C usa (input / output)

Se vuoi condividere tra B e C, puoi anche usare (input / output) ma si consiglia di usare Service.

b) Se l'albero è grande e complesso. (se ci sono così tanti livelli di connessioni genitore e figlio.) E in questo caso, se vuoi condividere i dati, suggerirei ngrx

Implementa l'architettura di flusso che crea un archivio lato client a cui qualsiasi componente può iscriversi e può aggiornarsi senza creare alcuna condizione di competizione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.