Per comprendere appieno il problema e le possibili soluzioni, dobbiamo discutere il rilevamento del cambiamento angolare - per tubi e componenti.
Rilevamento cambio tubo
Tubi apolidi / puri
Per impostazione predefinita, le pipe sono senza stato / pure. Le pipe senza stato / pure trasformano semplicemente i dati di input in dati di output. Non ricordano nulla, quindi non hanno proprietà, solo un transform()
metodo. Angular può quindi ottimizzare il trattamento dei tubi stateless / puri: se i loro input non cambiano, i tubi non devono essere eseguiti durante un ciclo di rilevamento delle modifiche. Per una pipe come {{power | exponentialStrength: factor}}
, power
e factor
sono input.
Per questa domanda, "#student of students | sortByName:queryElem.value"
, students
e queryElem.value
sono ingressi, e tubo sortByName
è stateless / puro. students
è un array (riferimento).
- Quando viene aggiunto uno studente, il riferimento all'array non cambia
students
, non cambia, quindi la pipe senza stato / pura non viene eseguita.
- Quando qualcosa viene digitato nell'input del filtro,
queryElem.value
cambia, quindi viene eseguita la pipe senza stato / pura.
Un modo per risolvere il problema dell'array è cambiare il riferimento all'array ogni volta che viene aggiunto uno studente, ovvero creare un nuovo array ogni volta che viene aggiunto uno studente. Potremmo farlo con concat()
:
this.students = this.students.concat([{name: studentName}]);
Sebbene funzioni, il nostro addNewStudent()
metodo non dovrebbe essere implementato in un certo modo solo perché stiamo usando una pipe. Vogliamo usare push()
per aggiungere al nostro array.
Stateful Pipes
Le pipe con stato hanno uno stato: normalmente hanno proprietà, non solo un transform()
metodo. Potrebbe essere necessario valutarli anche se i loro input non sono cambiati. Quando specifichiamo che un pipe è stateful / non puro - pure: false
- allora ogni volta che il sistema di rilevamento delle modifiche di Angular controlla un componente per le modifiche e quel componente utilizza un pipe stateful, controllerà l'output del pipe, indipendentemente dal fatto che il suo input sia cambiato o meno.
Questo suona come quello che vogliamo, anche se è meno efficiente, poiché vogliamo che la pipe venga eseguita anche se il students
riferimento non è cambiato. Se rendiamo semplicemente stateful la pipe, otteniamo un errore:
EXCEPTION: Expression 'students | sortByName:queryElem.value in HelloWorld@7:6'
has changed after it was checked. Previous value: '[object Object],[object Object]'.
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value
Secondo la risposta di @ drewmoore , "questo errore si verifica solo in modalità dev (che è abilitata per impostazione predefinita a partire dalla beta-0). Se chiami enableProdMode()
durante il bootstrap dell'app, l'errore non verrà generato." I documenti per loApplicationRef.tick()
stato:
In modalità sviluppo, tick () esegue anche un secondo ciclo di rilevamento delle modifiche per garantire che non vengano rilevate ulteriori modifiche. Se durante questo secondo ciclo vengono rilevate ulteriori modifiche, le associazioni nell'app hanno effetti collaterali che non possono essere risolti in un singolo passaggio di rilevamento delle modifiche. In questo caso, Angular genera un errore, poiché un'applicazione Angular può avere solo un passaggio di rilevamento delle modifiche durante il quale deve essere completato tutto il rilevamento delle modifiche.
Nel nostro scenario credo che l'errore sia fasullo / fuorviante. Abbiamo una pipe con stato e l'output può cambiare ogni volta che viene chiamato - può avere effetti collaterali e va bene. NgFor viene valutato dopo la pipe, quindi dovrebbe funzionare bene.
Tuttavia, non possiamo davvero sviluppare con questo errore generato, quindi una soluzione alternativa è aggiungere una proprietà di array (cioè, stato) all'implementazione del pipe e restituire sempre quell'array. Vedi la risposta di @ pixelbits per questa soluzione.
Tuttavia, possiamo essere più efficienti e, come vedremo, non avremo bisogno della proprietà array nell'implementazione del pipe e non avremo bisogno di una soluzione alternativa per il rilevamento del doppio cambiamento.
Rilevamento del cambio di componente
Per impostazione predefinita, su ogni evento del browser, il rilevamento delle modifiche angolari passa attraverso ogni componente per vedere se è cambiato: vengono controllati input e modelli (e forse altre cose?).
Se sappiamo che un componente dipende solo dalle sue proprietà di input (e dagli eventi del modello) e che le proprietà di input sono immutabili, possiamo utilizzare la onPush
strategia di rilevamento delle modifiche molto più efficiente . Con questa strategia, invece di controllare ogni evento del browser, un componente viene controllato solo quando cambiano gli input e quando si attivano gli eventi del modello. E, a quanto pare, non otteniamo Expression ... has changed after it was checked
quell'errore con questa impostazione. Questo perché un onPush
componente non viene controllato di nuovo finché non viene "contrassegnato" ( ChangeDetectorRef.markForCheck()
) di nuovo. Quindi le associazioni dei modelli e gli output delle pipe con stato vengono eseguiti / valutati solo una volta. Le pipe senza stato / pure non vengono ancora eseguite a meno che i loro input non cambino. Quindi abbiamo ancora bisogno di un tubo con stato qui.
Questa è la soluzione suggerita da @EricMartinez: stateful pipe con onPush
rilevamento delle modifiche. Vedi la risposta di @ caffinatedmonkey per questa soluzione.
Nota che con questa soluzione il transform()
metodo non deve restituire ogni volta lo stesso array. Lo trovo un po 'strano però: una pipe con stato senza stato. Pensandoci ancora un po '... la pipe con stato probabilmente dovrebbe sempre restituire lo stesso array. Altrimenti potrebbe essere utilizzato solo con onPush
componenti in modalità dev.
Quindi, dopo tutto ciò, penso che mi piaccia una combinazione delle risposte di @ Eric e @ pixelbits: pipe stateful che restituisce lo stesso riferimento di array, con onPush
rilevamento delle modifiche se il componente lo consente. Poiché la pipe con stato restituisce lo stesso riferimento all'array, la pipe può ancora essere utilizzata con componenti che non sono configurati con onPush
.
Plunker
Questo probabilmente diventerà un idioma Angular 2: se un array sta alimentando una pipe e l'array potrebbe cambiare (gli elementi nell'array che è, non il riferimento all'array), dobbiamo usare un pipe con stato.
pure:false
nel tuo tubo echangeDetection: ChangeDetectionStrategy.OnPush
nel tuo componente.