RxJS Beobachtbar: Abonnement verloren?

8

Was ist der Unterschied zwischen den folgenden zwei beobachtbaren Abbildungen?

(wenn dir etwas im folgenden Code seltsam erscheint: es stammt von einem Hobby-Hobby-Projekt; ich lerne immer noch RxJS)

Ich habe eine Komponente mit einem Getter und einem Konstruktor. Beide lesen Informationen aus dem ngrx-Store der App und extrahieren eine Zeichenfolge ( name ).

Der einzige Unterschied zwischen dem Getter und dem Konstruktor: Der Getter wird im HTML verwendet und die Observable, die zurückgegeben wird, wird über ein async pipe gesendet, während das beobachtbare Mapping im Konstruktor beendet ist durch ein Abonnement mit subscribe . Ich erwarte, dass beide so oft feuern, wie ein neuer Wert für name verfügbar wird.

Aber stattdessen funktioniert nur der Getter so und stellt das async pipe im HTML zur Verfügung, wo es mit neuen Werten des Namens verwendet wird ( console.log('A') wird für jede Namensänderung aufgerufen). Der Rückruf von subscribe wird nur einmal aufgerufen: console.log('B') und console.log('B!') werden beide genau einmal und nie wieder aufgerufen.

Wie kann dieser Unterschied im Verhalten erklärt werden?

Snippet von meiner Komponente:

%Vor%

Zusätzliche Information: Wenn ich ngOnInit hinzufüge, wird dieser Lebenszyklus-Hook während des gesamten Tests genau einmal aufgerufen. Wenn ich das Abonnement vom Konstruktor in den Lebenszyklus-Hook ngOnInit verschiebe, funktioniert es nicht besser als innerhalb des Konstruktors. Genau das gleiche (unerwartete) Verhalten. Gleiches gilt für ngAfterViewInit und weitere Lebenszyklus-Haken.

Ausgabe für den Namen ändert 'some-name' -> 'some-other-name' -> 'some-third-name' -> 'some-fourth-name' -> 'some-fifth-name' :

[UPDATE] wie von Pace in ihrem Kommentar vorgeschlagen, fügte ich Getter Anrufprotokolle

hinzu

[UPDATE] do s wurde hinzugefügt wie von Pace vorgeschlagen

%Vor%

Beispielinhalt der Ausgabe, die von console.log s in do s:

gedruckt wird %Vor%

Scheint so, als ob das Abonnement subscribe nach dem ersten Aufruf verloren geht. Aber warum?

    
ideaboxer 25.01.2018, 22:07
quelle

2 Antworten

1

Das von ngrx.select() zurückgegebene Observable wird nur ausgelöst, wenn sich die Daten im Speicher geändert haben.

Wenn das Observable ausgelöst werden soll, wenn sich initialName ändert, würde ich empfehlen, initialName in ein RXJS Subject zu konvertieren und combineLatest zu verwenden:

%Vor%     
Miller 06.02.2018, 07:32
quelle
10

Sie sollten niemals einen solchen Getter verwenden. Gibt nicht ein Observable von einem Getter zurück.

Angular wird sich immer wieder abmelden / abonnieren, wenn ein Änderung Erkennungszyklus passiert (was viel passiert).

Für jetzt schreibe ich "CD" für "Change Detection"

Einfache Demo davon:

Nehmen Sie eine wirklich einfache Komponente:

%Vor%

Sie werden getting a new value in Ihrer Konsole sehen, und jedes Mal, wenn Sie auf die Schaltfläche "Click to trigger change detection" klicken, bei der ein (click) Event registriert ist, wird ein neuer CD-Zyklus ausgelöst.

Und wenn Sie auf diese Schaltfläche klicken, sehen Sie, dass Sie zweimal getting a new value erhalten. (Zweimal, weil wir nicht im Produktionsmodus sind und Angular 2 CD-Zyklen ausführt, um sicherzustellen, dass sich die Variable zwischen der ersten und zweiten Änderungserkennung nicht geändert hat, was zu Problemen führen kann, aber das ist eine andere Geschichte).

Der Punkt von einem Observablen ist, dass er für eine lange Zeit offen bleibt und Sie sollten davon profitieren. Um den vorherigen Code umzuformatieren, damit das Abonnement geöffnet bleibt und um das erneute Abmelden / Abonnieren zu vermeiden, können wir den Getter einfach loswerden und eine öffentliche Variable deklarieren (zugänglich durch die Vorlage):

%Vor%

Und nun, egal wie oft Sie auf die Schaltfläche klicken, sehen Sie ein und nur ein getting a new value (bis das Observable natürlich einen neuen Wert ausgibt), aber die Änderungserkennung wird nicht triggert ein neues Abonnement.

Hier ist eine Live-Demo auf Stackblitz , damit du herumspielen und die console.log happening = sehen kannst) Ссылка

BEARBEITEN: A getter ist eine Funktion, und daher muss Angular sie auf jeder CD aufrufen, um zu prüfen, ob ein neuer Wert von ihr kommt, der in der Ansicht aktualisiert werden soll. Das kostet viel, aber es ist das Prinzip und die "Magie" des Rahmens. Aus diesem Grund sollten Sie es vermeiden, intensive CPU-Aufgaben in einer Funktion auszuführen, die auf jeder CD ausgelöst werden könnte. Wenn es eine reine Funktion ist (gleiche Eingabe gleiche Ausgabe UND keine Nebeneffekte), verwenden Sie eine Pipe, da sie standardmäßig als "rein" betrachtet werden und die Ergebnisse zwischenspeichern. Für dieselben Argumente werden sie die Funktion in der Pipe nur einmal ausführen, das Ergebnis zwischenspeichern und dann das Ergebnis sofort zurückgeben, ohne die Funktion erneut auszuführen.

    
Maxime 02.02.2018 09:44
quelle