Ответ 1
Чтобы полностью понять проблему и возможные решения, нам нужно обсудить обнаружение изменений Angular - для труб и компонентов.
Обнаружение изменения труб
Безстоящие/чистые трубы
По умолчанию трубы являются безстоящими/чистыми. Безстоящие/чистые трубы просто преобразуют входные данные в выходные данные. Они ничего не помнят, поэтому у них нет никаких свойств – просто метод transform()
. Angular может поэтому оптимизировать обработку безстоящих/чистых труб: если их входы не меняются, трубы не должны выполняться во время цикла обнаружения изменений. Для трубы, такой как {{power | exponentialStrength: factor}}
, power
и factor
находятся входы.
Для этого вопроса "#student of students | sortByName:queryElem.value"
, students
и queryElem.value
являются входами, а pipe sortByName
- безстоящим/чистым. students
- массив (ссылка).
- Когда ученик добавлен, ссылка на массив не изменяется –
students
не изменяется – следовательно, не состоящий в статусе/чистой трубе. - Когда что-то вводится в вход фильтра,
queryElem.value
изменяется, поэтому выполняется безстоящий/чистый канал.
Один из способов исправить проблему с массивом - изменить ссылку на массив каждый раз, когда добавлен студент; ndash; т.е. создавать новый массив каждый раз, когда ученик добавляется. Мы могли бы сделать это с помощью concat()
:
this.students = this.students.concat([{name: studentName}]);
Хотя это работает, наш метод addNewStudent()
не должен реализовываться определенным способом только потому, что мы используем канал. Мы хотим использовать push()
для добавления в наш массив.
Сотовые трубки
Состоятельные трубы имеют состояние - они обычно имеют свойства, а не только метод transform()
. Их, возможно, придется оценивать, даже если их исходные данные не изменились. Когда мы укажем, что труба имеет состояние/нечистое ndash; pure: false
– то всякий раз, когда система обнаружения изменений Angular проверяет компонент для изменений, и этот компонент использует канал с состоянием, он проверяет вывод этого канала, изменил ли его вход или нет.
Это похоже на то, что мы хотим, хотя оно менее эффективно, так как мы хотим, чтобы труба выполнялась, даже если ссылка students
не изменилась. Если мы просто сделаем трубку работоспособной, мы получим ошибку:
EXCEPTION: Expression 'students | sortByName:queryElem.value in [email protected]:6'
has changed after it was checked. Previous value: '[object Object],[object Object]'.
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value
Согласно @drewmoore answer, эта ошибка происходит только в режиме dev (который по умолчанию включен по умолчанию на бета-0). Если вы вызываете enableProdMode()
при загрузке приложения ошибка не будет выбрана ". docs для ApplicationRef.tick()
:
В режиме разработки tick() также выполняет второй цикл обнаружения изменений, чтобы гарантировать, что дальнейшие изменения не обнаружены. Если во время этого второго цикла будут получены дополнительные изменения, привязки в приложении будут иметь побочные эффекты, которые не могут быть разрешены в одном проходе обнаружения изменений. В этом случае Angular выдает ошибку, так как приложение Angular может иметь только один проход обнаружения изменений, в течение которого все изменения обнаружения должны быть завершены.
В нашем сценарии я считаю ошибку ложной/вводящей в заблуждение. У нас есть канал с состоянием, и выход может меняться каждый раз, когда он называется – он может иметь побочные эффекты, и все в порядке. NgFor оценивается после трубы, поэтому она должна работать нормально.
Однако мы не можем развиваться с этой ошибкой, поэтому один способ заключается в том, чтобы добавить свойство массива (т.е. состояние) к реализации канала и всегда возвращать этот массив. См. Ответ @pixelbits для этого решения.
Однако мы можем быть более эффективными, и, как мы увидим, нам не понадобится свойство массива в реализации канала, и нам не понадобится обходной путь для обнаружения двойного изменения.
Обнаружение изменений компонентов
По умолчанию в каждом событии браузера, Angular обнаружение изменений проходит через каждый компонент, чтобы увидеть, изменилось ли оно – входы и шаблоны (и, возможно, другие вещи?).
Если мы знаем, что компонент зависит только от его входных свойств (и событий шаблона) и что входные свойства неизменяемы, мы можем использовать гораздо более эффективную стратегию обнаружения изменений onPush
. С помощью этой стратегии вместо проверки на каждое событие браузера компонент проверяется только тогда, когда входы меняются и когда запускаются события шаблона. И, по-видимому, мы не получаем эту ошибку Expression ... has changed after it was checked
с этим параметром. Это связано с тем, что компонент onPush
не проверяется снова, пока он не будет "отмечен" (ChangeDetectorRef.markForCheck()
) еще раз. Таким образом, привязки шаблонов и выходы со стороны состояния выполняются/оцениваются только один раз. Безстоящие/чистые трубы все еще не выполняются, если их входы не меняются. Таким образом, нам все еще нужна эталонная труба.
Это решение @EricMartinez предложило: stateful pipe с обнаружением изменений onPush
. См. Ответ @caffinatedmonkey для этого решения.
Обратите внимание, что при этом решении метод transform()
не должен возвращать один и тот же массив каждый раз. Я нахожу, что это немного странно: трубка с состоянием без состояния. Думая об этом еще немного... трубка с состоянием, вероятно, всегда должна возвращать тот же массив. В противном случае его можно использовать только с компонентами onPush
в режиме dev.
Итак, после всего этого, мне кажется, мне нравится сочетание ответов @Eric и @pixelbits: stateful pipe, который возвращает ту же ссылку на массив, с onPush
обнаружением изменений, если компонент позволяет это. Поскольку труба stateful возвращает тот же самый массив, труба все еще может использоваться с компонентами, которые не настроены с помощью onPush
.
Это, вероятно, станет идиомой Angular 2: если массив подает канал, и массив может измениться (элементы в массиве, а не ссылка на массив), нам нужно использовать канал с состоянием.