ChangeDetectionStrategy.OnPush and Observable.subscribe in Angular 2

Sto cercando di ChangeDetectionStrategy.OnPush best practice quando utilizzo Observables insieme a ChangeDetectionStrategy.OnPush .

L’esempio dimostra lo scenario comune di voler mostrare qualche tipo di messaggio di caricamento (o forse una semplice animazione di spinner):

Plnkr qui

 @Component({ selector: 'my-app', template: `Are we loading?: {{loadingMessage}}`, // Obviously "Default" will notice the change in `loadingMessage`... // changeDetection: ChangeDetectionStrategy.Default // But what is best practice when using OnPush? changeDetection: ChangeDetectionStrategy.OnPush }) export class App implements OnInit { private loadingMessage = "Wait for it..."; constructor() { } ngOnInit() { // Pretend we're loading data from a service. // This component is only interested in the call's success Observable.of(true) .delay(2000) .subscribe(success => { if(success){ console.log('Pretend loading: success!'); // OnPush won't detect this change this.loadingMessage = 'Success!'; } }); } } 

Comprendo più o meno il requisito dell’immutabilità con OnPush e, almeno per me, ha attualmente senso quando si parla di dati di modello reali (probabilmente contenuti in una sorta di archivio).

Quindi, ho due domande:

  1. Perché non l’assegnazione del nuovo valore di stringa 'Success!' triggersre il rilevatore di modifiche? Per quanto riguarda l’immutabilità, il valore è cambiato, giusto?
  2. Come dovrebbe essere implementato lo stato del componente interno leggero (ad esempio loadingMessage ) quando si utilizza ChangeDetectionStrategy.OnPush ? Se ci sono più buone pratiche, per favore indicami la giusta direzione.

Buona domanda. Ho due citazioni da Savkin su onPush (dato che i documenti di Angular.io non sembrano avere ancora informazioni su questo argomento):

Il framework controllerà i componenti di OnPush solo quando i loro input cambiano oi modelli dei componenti emettono eventi. – rif

Quando si utilizza OnPush , Angular controllerà il componente solo quando una delle sue proprietà di input cambia, quando triggers un evento o quando un osservabile triggers un evento. – ref (in un commento rispondi a @vivainio)

La seconda citazione sembra più completa. (Peccato che sia stato sepolto in un commento!)

Perché non l’assegnazione del nuovo valore di stringa Success! triggersre il rilevatore di modifiche? Per quanto riguarda l’immutabilità, il valore è cambiato, giusto?

OnPush immutabilità di OnPush è in riferimento alle proprietà di input, non alle normali proprietà di istanza. Se loadingMessage era una proprietà di input e il valore è stato modificato, il rilevamento delle modifiche veniva eseguito sul componente. (Vedi @ risposta di Vlado per Plunker.)

Come deve essere implementato lo stato del componente interno leggero (ad esempio, loadingMessage ) quando si utilizza ChangeDetectionStrategy.OnPush ? Se ci sono più buone pratiche, per favore indicami la giusta direzione.

Ecco quello che so finora:

  • Se cambiamo lo stato interno come parte della gestione di un evento o parte di un tiro osservabile, il rilevamento delle modifiche viene eseguito sul componente OnPush (cioè, la vista verrà aggiornata). Nel tuo caso particolare, sono rimasto sorpreso dal fatto che la vista non si aggiornasse. Ecco due ipotesi sul perché no:
    • Aggiungendo delay() , Angular lo guarda più come un setTimeout() piuttosto che un cambiamento osservabile. setTimeout s non comporta l’esecuzione di rilevamento modifiche su un componente OnPush . Nel tuo esempio il rilevatore di modifiche ha completato il suo lavoro 2 secondi prima che il valore di loadingMessage venga modificato.
    • Come @Sasxa mostra nella sua risposta, devi avere un legame nel modello con l’ Observable . Cioè, forse è più che “un fuoco osservabile” … forse deve essere un osservabile legato che spara. In tal caso, la creazione di loadingMessage (o anche di una proprietà di state più generica) come Observable ti consentirà di associare il modello al suo valore (o più valori asincroni), vedi questo esempio plnkr .
      Aggiornamento 2016-04-28: sembra che l’associazione debba includere | async | async , come mostrato nel plnkr, e in questo plnkr .
  • Se cambiamo lo stato interno e non fa parte della gestione degli eventi o di un fuoco osservabile, possiamo iniettare ChangeDetectorRef nel nostro componente e chiamare il metodo markForCheck() per causare il rilevamento delle modifiche da eseguire sul componente OnPush e su tutti i componenti degli antenati fino alla radice componente. Se viene modificato solo lo stato di visualizzazione (cioè, lo stato che è locale rispetto al componente e forse i suoi discendenti), invece, è ansible utilizzare detectChanges() , che non contrassegnerà tutti i componenti degli antenati per il rilevamento delle modifiche. Plunker

AFAIK, OnPush viene utilizzato quando si lavora direttamente con osservabili :

 //our root app component import {Component, OnInit, ChangeDetectionStrategy} from 'angular2/core' import {Observable} from 'rxjs/Observable'; import 'rxjs/Rx'; @Component({ selector: 'my-app', template: `Are we loading?: {{ loadingMessage |async }}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class App implements OnInit { private loadingMessage; constructor() { } ngOnInit() { this.loadingMessage = Observable.of(true) .delay(2000) } } 

Con questo ChangeDetectionStrategy.OnPush stai dicendo al tuo componente di ascoltare solo le modifiche sulle sue proprietà di input.

Ho aggiunto il componente di caricamento al tuo esempio solo per mostrarti come funziona: http://plnkr.co/edit/k5tkmcLlzkwz4t5ifqFD?p=preview