Aby w pełni zrozumieć problem i możliwe rozwiązania, musimy omówić wykrywanie zmian kątowych - dla rur i komponentów.
Wykrywanie zmian rur
Rury bezpaństwowe / czyste
Domyślnie potoki są bezstanowe / czyste. Potoki bezstanowe / czyste po prostu przekształcają dane wejściowe w dane wyjściowe. Nic nie pamiętają, więc nie mają żadnych właściwości - tylko transform()
metoda. Angular może zatem zoptymalizować traktowanie rur bezstanowych / czystych: jeśli ich dane wejściowe się nie zmieniają, rury nie muszą być wykonywane podczas cyklu wykrywania zmian. W przypadku rur takich jak {{power | exponentialStrength: factor}}
, power
ifactor
są wejściami.
W przypadku tego pytania "#student of students | sortByName:queryElem.value"
, students
iqueryElem.value
to wejścia, a przewód sortByName
jest bezstanowy / czystego. students
jest tablicą (odniesieniem).
- Po dodaniu ucznia tablica odwołanie nie zmienia się -
students
nie zmienia się - dlatego potok bezstanowy / czysty nie jest wykonywany.
- Kiedy coś jest wpisane na wejściu filtru,
queryElem.value
zmienia się, dlatego wykonywany jest potok bezstanowy / czysty.
Jednym ze sposobów rozwiązania problemu z tablicą jest zmiana odwołania do tablicy za każdym razem, gdy dodawany jest uczeń, tj. Tworzenie nowej tablicy za każdym razem, gdy dodawany jest uczeń. Moglibyśmy to zrobić za pomocą concat()
:
this.students = this.students.concat([{name: studentName}]);
Chociaż to działa, nasz addNewStudent()
metoda nie powinna być implementowana w określony sposób tylko dlatego, że używamy potoku. Chcemy push()
dodać do naszej tablicy.
Stateful Pipes
Rury stanowe mają stan - zwykle mają właściwości, a nie tylko transform()
metodę. Mogą wymagać oceny, nawet jeśli ich dane wejściowe nie uległy zmianie. Kiedy określimy, że potok jest stanowy / nieczysty -pure: false
- wtedy za każdym razem, gdy system wykrywania zmian Angulara sprawdza komponent pod kątem zmian i ten komponent używa potoku stanowego, sprawdza wyjście potoku, czy jego wejście uległo zmianie, czy nie.
Brzmi to tak, jak chcemy, nawet jeśli jest mniej wydajne, ponieważ chcemy, aby potok był wykonywany, nawet jeśli students
odniesienie nie uległo zmianie. Jeśli po prostu sprawimy, że potok będzie stanowy, otrzymamy błąd:
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
Zgodnie z odpowiedzią @ drewmoore , „ten błąd występuje tylko w trybie programisty (który jest domyślnie włączony od wersji beta-0). Jeśli zadzwonisz enableProdMode()
podczas ładowania aplikacji, błąd nie zostanie zgłoszony”. Dokumenty dotycząceApplicationRef.tick()
stanu:
W trybie programowania tick () wykonuje również drugi cykl wykrywania zmian, aby zapewnić, że żadne dalsze zmiany nie zostaną wykryte. Jeśli w tym drugim cyklu zostaną wykryte dodatkowe zmiany, powiązania w aplikacji mają skutki uboczne, których nie można rozwiązać w pojedynczym przebiegu wykrywania zmian. W takim przypadku Angular zgłasza błąd, ponieważ aplikacja Angular może mieć tylko jeden przebieg wykrywania zmiany, podczas którego musi zostać zakończone wszystkie wykrywanie zmian.
W naszym scenariuszu uważam, że błąd jest fałszywy / wprowadzający w błąd. Mamy potok stanowy i wyjście może się zmieniać za każdym razem, gdy jest wywoływane - może mieć efekty uboczne i to jest w porządku. NgFor jest oceniane po potoku, więc powinno działać poprawnie.
Jednak tak naprawdę nie możemy programować po wyrzuceniu tego błędu, więc jednym obejściem jest dodanie właściwości tablicy (tj. Stan) do implementacji potoku i zawsze zwracanie tej tablicy. Zobacz odpowiedź @ pixelbits na to rozwiązanie.
Możemy jednak być bardziej wydajni i, jak zobaczymy, nie będziemy potrzebować właściwości tablicy w implementacji potoku i nie będziemy potrzebować obejścia dla wykrywania podwójnej zmiany.
Wykrywanie zmian komponentów
Domyślnie przy każdym zdarzeniu przeglądarki wykrywanie zmian kątowych przechodzi przez każdy komponent, aby sprawdzić, czy się zmienił - sprawdzane są dane wejściowe i szablony (a może inne rzeczy?).
Jeśli wiemy, że składnik zależy tylko od jego właściwości wejściowych (i zdarzeń szablonu), a właściwości wejściowe są niezmienne, możemy zastosować znacznie wydajniejszą onPush
strategię wykrywania zmian. Dzięki tej strategii, zamiast sprawdzania każdego zdarzenia przeglądarki, komponent jest sprawdzany tylko wtedy, gdy zmieniają się dane wejściowe i gdy wyzwalają się zdarzenia szablonu. I najwyraźniej nie otrzymujemy tego Expression ... has changed after it was checked
błędu przy tym ustawieniu. Dzieje się tak, ponieważ onPush
składnik nie jest ponownie sprawdzany, dopóki nie zostanie ponownie „zaznaczony” ( ChangeDetectorRef.markForCheck()
). Dlatego powiązania szablonów i stanowe dane wyjściowe potoku są wykonywane / oceniane tylko raz. Potoki bezstanowe / czyste nadal nie są wykonywane, chyba że zmienią się ich dane wejściowe. Więc nadal potrzebujemy tu stanowej potoku.
Oto rozwiązanie zasugerowane przez @EricMartinez: potok stanowy z onPush
wykrywaniem zmian. Zobacz odpowiedź @ caffinatedmonkey dla tego rozwiązania.
Zauważ, że w tym rozwiązaniu transform()
metoda nie musi za każdym razem zwracać tej samej tablicy. Uważam to jednak za trochę dziwne: potok stanowy bez stanu. Myśląc o tym więcej ... potok stanowy prawdopodobnie powinien zawsze zwracać tę samą tablicę. W przeciwnym razie mógłby być używany tylko z onPush
komponentami w trybie deweloperskim.
Po tym wszystkim, myślę, że podoba mi się kombinacja odpowiedzi @ Erica i @ pixelbits: potok stanowy, który zwraca to samo odniesienie do tablicy, z onPush
wykrywaniem zmian, jeśli komponent na to pozwala. Ponieważ potok stanowy zwraca to samo odwołanie do tablicy, potok może być nadal używany z komponentami, które nie są skonfigurowane z onPush
.
Plunker
Prawdopodobnie stanie się to idiomem Angulara 2: jeśli tablica zasila potok, a tablica może się zmienić (elementy w tablicy, a nie odwołanie do tablicy), musimy użyć potoku stanowego.
pure:false
w swojej rurze ichangeDetection: ChangeDetectionStrategy.OnPush
komponencie.