Aktualisieren von Variablenänderungen in Komponenten aus einem Dienst mit angle2

77

Meine App hat einen NameService, der den Namen enthält.

Es gibt zwei untergeordnete Komponenten von App, Navbar und TheContent, die auf diesen Dienst verweisen. Immer wenn sich der Name im Dienst ändert, möchte ich, dass er in beiden anderen Komponenten aktualisiert wird. Wie kann ich das machen?

import {Component, Injectable} from 'angular2/core'

// Name Service

@Injectable()
class NameService {
  name: any;
  constructor() {
    this.name = "Jack";
  }
  change(){
    this.name = "Jane";
  }
}

// The navbar
@Component({
  selector: 'navbar',
  template: '<div>This is the navbar, user name is {{name}}.</div>'
})
export class Navbar {
  name: any;
  constructor(nameService: NameService) {
    this.name = nameService.name;
  }
}

// The content area
@Component({
  selector: 'thecontent',
  template: '<div>This is the content area. Hello user {{name}}. <button (click)=changeMyName()>Change the name</button></div>'
})
export class TheContent {

  name: any;

  constructor(public nameService: NameService) {
    this.name = nameService.name;
  }
  changeMyName() {
       this.nameService.change();
     console.log(this.nameService.name);
  }
}


@Component({
  selector: 'app',
  providers: [NameService],
  directives: [TheContent, Navbar],
  template: '<navbar></navbar><thecontent></thecontent>'
})
export class App {
  constructor(public nameService: NameService) {
  }
}
B Rumpf
quelle

Antworten:

104

Stellen Sie ein Ereignis im Dienst bereit und abonnieren Sie es in den Komponenten:

@Injectable()
class NameService {
  name: any;
  // EventEmitter should not be used this way - only for `@Output()`s
  //nameChange: EventEmitter<string> = new EventEmitter<string>();
  nameChange: Subject<string> = new Subject<string>();
  constructor() {
    this.name = "Jack";
  }
  change(){
    this.name = 'Jane';
    this.nameChange.next(this.name);
  }
}
export class SomeComponent { 
  constructor(private nameService: NameService) {
    this.name = nameService.name;
    this._subscription = nameService.nameChange.subscribe((value) => { 
      this.name = value; 
    });
  }

  ngOnDestroy() {
   //prevent memory leak when component destroyed
    this._subscription.unsubscribe();
  }
}

Siehe auch
angle.io - KOMPONENTEN-INTERAKTION - Eltern und Kinder kommunizieren über einen Dienst

Günter Zöchbauer
quelle
1
Denken Sie auch daran, das Ereignis in der Komponente abzubestellen. Andernfalls wird die Komponenteninstanz nicht freigegeben. Sie müssen das von der Abonnementfunktion zurückgegebene Objekt abbestellen.
Chandermani
3
let subscription=nameService.nameChange.subscribe((value) => { this.name = value; }); subscription.unsubscribe(). Ich habe mich diesem Problem gestellt, daher der Haftungsausschluss. Wenn das Abonnement von einer anderen Methode gekündigt werden muss, sollte die Variable auf Klassenebene sein
Chandermani
1
@micronyks Bei der Frage ging es nicht darum, den Wert beim erneuten Laden IMHO beizubehalten. Dazu müssen Sie die Daten auf einem Server speichern und neu laden oder zumindest localstorage verwenden, um sie im Browser zu speichern.
Günter Zöchbauer
1
Ich habe versucht, diese Lösung zu implementieren, aber im Dienst wird eine Fehlermeldung angezeigt property 'emit' does not exist on type 'Subject<string>'. Vermisse ich etwas in der obigen Lösung? Gibt es einen funktionierenden Plunkr dieser Lösung?
Steve Schrab
3
@Scipion Wenn Sie sich unbedingt anmelden, sollten Sie sich im Allgemeinen auch unbedingt abmelden. In diesem Fall würde die Komponente, die diesen Dienst abonniert, wahrscheinlich den Rückruf ausführen, der subscribe()bei der Namensänderung übergeben wird, selbst wenn er bereits zerstört ist, da das Abonnement aktiv bleibt, bis die Komponente Müll gesammelt wird, was wahrscheinlich erst nach dem Abonnement geschieht abgesagt.
Günter Zöchbauer
23

Da es sich bei namein NameServiceum einen primitiven Typ handelt, erhalten Sie eine andere Instanz im Dienst und in Ihren Komponenten. Wenn Sie ändern namein NameService, haben die Bauteileigenschaften noch den Anfangswert und die Bindung nicht funktioniert wie erwartet.

Sie sollten hier die winkel1 "Punktregel" anwenden und an einen Referenztyp binden. Ändern Sie NameServicediese Option , um ein Objekt zu speichern, das den Namen enthält.

export interface Info {
   name:string;
}

@Injectable()
class NameService {
  info: Info = { name : "Jack" };
  change(){
    this.info.name = "Jane";
  }
}

Sie können an dieses Objekt binden und nameautomatisch Aktualisierungen der Eigenschaft erhalten.

// The navbar
@Component({
  selector: 'navbar',
  template: '<div>This is the navbar, user name is {{info.name}}.</div>'
})
export class Navbar {
  info: Info;
  constructor(nameService: NameService) {
    this.info = nameService.info;
  }
}
hansmaad
quelle
So würde ich es auch machen. In Winkel 1 ist "Immer einen Punkt in Ihrem Modell haben" ein guter Weg, um den Aufwand für die Synchronisierung Ihrer Bindungen zu verringern. Je nach Anwendungsfall kann dies auch in Winkel 2 angemessen sein. Ohne diese Option kann die Verwendung von Ereignis-Emittern zum Synchronisieren des Modells mühsam werden.
Pixelbits
Wow, warum funktioniert das im Vergleich zum EventEmitter? es fühlt sich nach so viel weniger Arbeit an?
B Rumpf
7
@micronyks In angle2 können Sie mehrere Dienstinstanzen haben. Sie sollten nicht providersbeide Komponenten definieren . Auf diese Weise hat jede Komponente ihre eigene NameService plnkr.co/edit/nScZgT1hsIKHZ7Z5E6u3?p=preview
hansmaad
3
@BHull, es funktioniert, weil bei einem Objekt sowohl der Dienst als auch die Komponente auf dasselbe Objekt verweisen ... und sie jeweils einen Verweis auf dieses eine Objekt haben. Bei einem primitiven Typ (Zeichenfolge, Zahl, Boolescher Wert) erhält die Komponente eine eigene Kopie des primitiven Werts im Konstruktor. Jede Änderung, die Sie an der primitiven nameEigenschaft im Service vornehmen, steht in keinem Zusammenhang mit Änderungen, die Sie an der primitiven nameEigenschaft in der Komponente vornehmen .
Mark Rajcok
1
„Wenn Sie ändern namein NameServiceSie eine andere Instanz im Dienst und Ihre Komponenten habe.“ Auch wenn Sie den Wert nicht ändern, erhält die Komponente nameim Konstruktor eine eigene primitive Eigenschaft. Der Anfangswert dieser neuen primitiven Eigenschaft ist der Wert der nameEigenschaft im Service zum Zeitpunkt der Zuweisung. Der Punkt, den ich versuche, ist, dass Sie mit primitiven Typen nie dieselbe "Instanz" haben. Bei einem mit Referenztypen können Sie zwei (oder mehr) Dinge erhalten, die auf dieselbe Instanz verweisen.
Mark Rajcok
12

Ich denke, dass die von Günter bereitgestellte Lösung die beste ist.

Sie müssen sich jedoch bewusst sein, dass Angular2-Dienste Singleton-Dienste sind, die in einem Baum von Injektoren stattfinden. Dies bedeutet, dass:

  • Wenn Sie Ihren Service auf Anwendungsebene definieren (innerhalb des zweiten Parameters der bootstrapMethode), kann die Instanz von allen Elementen (Komponenten und Service) gemeinsam genutzt werden.
  • Wenn Sie Ihren Service auf Komponentenebene (innerhalb des providersAttributs) definieren, ist die Instanz spezifisch für die Komponente und ihre Unterkomponenten.

Weitere Einzelheiten zu diesem Aspekt finden Sie im Dokument "Hierarchical Dependency Injection": https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html

Hoffe es hilft dir, Thierry

Thierry Templier
quelle