Wie teile ich Daten zwischen Komponenten in Angular 2?

84

In Angular 1.xx fragen Sie einfach nach demselben Dienst und erhalten dieselbe Instanz, sodass Sie die Daten im Dienst gemeinsam nutzen können.

Jetzt in Angular 2 habe ich eine Komponente, die einen Verweis auf meinen Dienst enthält. Ich kann die Daten im Dienst lesen und ändern, was gut ist. Wenn ich versuche, denselben Dienst in eine andere Komponente einzufügen, scheint es, als würde ich eine neue Instanz erhalten.

Was mache ich falsch? Ist das Muster selbst falsch (Verwenden eines Dienstes zum Teilen von Daten) oder muss ich den Dienst als Singleton (innerhalb einer Instanz der App) oder so markieren?

Ich bin 2.0.0-alpha.27/ übrigens auf

Ich füge einen Service durch appInjector(edit: now providers) in die @ComponentAnnotation ein und speichere dann eine Referenz im Konstruktor. Es funktioniert lokal in der Komponente - nur nicht komponentenübergreifend (sie teilen nicht dieselbe Dienstinstanz), wie ich es mir vorgestellt habe.

UPDATE : Ab Angular 2.0.0 haben wir jetzt @ngModule, wo Sie den Service unter der providersEigenschaft auf besagten definieren würden @ngModule. Dadurch wird sichergestellt, dass dieselbe Instanz dieses Dienstes an jede Komponente, jeden Dienst usw. in diesem Modul übergeben wird. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

UPDATE : Angular- und FE-Entwicklung im Allgemeinen ist viel passiert. Wie @noririco erwähnt hat, können Sie auch ein Statusverwaltungssystem wie NgRx verwenden: https://ngrx.io/

Per Hornshøj-Schierbeck
quelle
Sechs Methoden für den Datenaustausch zwischen eckigen Komponenten: - angulartutorial.net/2017/12/…
Prashobh
Wenn Sie hierher kommen, sollten Sie ein STATE Management System verwenden
noririco

Antworten:

63

Ein Service-Singleton ist eine gute Lösung. Anderer Weg - data/events bindings.

Hier ist ein Beispiel für beides:

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

Live ansehen

Alexander Ermolov
quelle
20
Ich habe es herausgefunden. Sie fügen nur eine Dienstinstanz ein - in 'App'. Dieselbe Instanz wird automatisch vererbt, wenn der Parameter zu den untergeordneten Konstruktoren hinzugefügt wird :) Ich habe den Fehler gemacht, den untergeordneten Komponenten einen weiteren appInjector hinzuzufügen, der neue Instanzen erstellt.
Per Hornshøj-Schierbeck
1
@AlexanderCrush könnten Sie Ihre Antwort aktualisieren? Da in späteren Alpha-Versionen (Alpha 30+) appInjector entfernt wurde . Die richtige Antwort sollte vorerst sein viewInjector.
Eric Martinez
1
@EricMartinez danke, Antwort und Plunker wurden aktualisiert.
Alexander Ermolov
1
Interessante Ressource, um zu verstehen, warum und wie dies funktioniert: blog.ilsttram.io/angular/2015/08/20/… .
Sojuka
2
Ich hatte das gleiche Problem, weil ich den Service in die Haupt-App und auch in die Komponente selbst injiziert habe providers: [MyService]. Durch das Entfernen der Anbieter wurde es die einzige Instanz der App
maufarinelli
43

Der Kommentar von @maufarinelli verdient eine eigene Antwort, denn bis ich ihn sah, schlug ich mit diesem Problem immer noch meinen Kopf gegen die Wand, selbst mit der Antwort von @Alexander Ermolov.

Das Problem ist, dass, wenn Sie ein providerszu Ihrem hinzufügen component:

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

Dies führt dazu, dass eine neue Instanz Ihres Dienstes injiziert wird ... anstatt ein Singleton zu sein .

Entfernen Sie also alle Instanzen Ihrer providers: [MyService]in Ihrer Anwendung außer in der module, und es wird funktionieren!

Serj Sagan
quelle
2
Nur ein Kommentar, es ist nie ein Singleton - es ist nur dieselbe Instanz, die herumgereicht wird. Sie könnten noch eine neue Instanz anfordern ...
Per Hornshøj-Schierbeck
10

Sie müssen die Ein- und Ausgänge eines @ Component-Dekorators verwenden. Hier ist das grundlegendste Beispiel für die Verwendung von beiden.

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
quelle
7
Es besteht keine Notwendigkeit zu importieren ngFor,
Richard Hamilton
7

In der übergeordneten Komponentenvorlage:

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

In der untergeordneten Komponente:

@Input() hero: Hero;

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

Alexis Gamarra
quelle
Möglicherweise, aber dies würde mehr Details erfordern. In der realen Welt ist es nicht so einfach. Stellen Sie sich vor, Sie haben eine Klasse, die Sie für mehrere Komponenten freigeben und auf Daten zugreifen möchten. das funktioniert nicht.
Sancelot
Ich verwende diesen Ansatz in einer großen Lösung, um Daten zwischen vielen Komponenten auszutauschen. Sie können viele Kinder haben und jedes erhält das gleiche Objekt. Haben Sie versucht, dies zu tun, bevor Sie sagten, dass es nicht funktioniert?
Alexis Gamarra
Ja, habe ich . es wird funktionieren .... aber mit einigen "Hackings", um einige Probleme zu lösen. Ihre Antwort erlaubt niemandem, sie zu verwenden.
Sancelot
2

Es gibt viele Wege. Dies ist ein Beispiel für die Weitergabe zwischen übergeordneten und untergeordneten Elementen. Das ist sehr effizient.

Ich habe ein Beispiel eingereicht, mit dem die Verwendung der Datenbindung auf zwei Arten in zwei Formularen angezeigt werden kann. Wenn jemand eine Plunkr-Probe zur Verfügung stellen kann, wäre das sehr schön ;-)

Möglicherweise suchen Sie mit einem Dienstanbieter nach einem anderen Weg. Sie können sich auch dieses Video als Referenz ansehen: ( Daten zwischen Komponenten in Angular austauschen )

mymodel.ts (Daten zum Teilen)

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

Denken Sie daran: Es muss ein übergeordnetes Element vorhanden sein, das "mein Modell" für untergeordnete Komponenten freigibt.

Übergeordnete Komponente

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

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

Hinweis: In einigen seltenen Fällen kann es zu Fehlern kommen, wenn der HTML-Code analysiert wird, da das Modell bei der Initialisierung der Seite nicht "bereit" ist. Stellen Sie in diesem Fall dem HTML-Code eine ngIf-Bedingung voran:

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

Es kommt darauf an, ob es einen einfachen Fall gibt

a) A -> B -> C A hat zwei untergeordnete B und C und wenn Sie Daten zwischen A und B oder A und C teilen möchten, verwenden Sie (Eingabe / Ausgabe)

Wenn Sie zwischen B und C teilen möchten, können Sie auch (Eingabe / Ausgabe) verwenden, es wird jedoch empfohlen, Service zu verwenden.

b) Wenn der Baum groß und komplex ist. (Wenn es so viele Ebenen von Eltern- und Kinderverbindungen gibt.) Und in diesem Fall, wenn Sie Daten gemeinsam nutzen möchten, würde ich ngrx vorschlagen

Es implementiert die Flussarchitektur, die einen clientseitigen Speicher erstellt, den jede Komponente abonnieren und aktualisieren kann, ohne eine Race-Bedingung zu erstellen.

Pratiyush
quelle