Чтобы полностью понять проблему и возможные решения, нам нужно обсудить обнаружение изменений Angular - для каналов и компонентов.
Обнаружение замены трубы
Каналы без сохранения состояния / чистые
По умолчанию каналы не имеют состояния / чистые. Каналы без сохранения состояния / чистые каналы просто преобразуют входные данные в выходные данные. Они ничего не помнят, поэтому у них нет никаких свойств - только transform()
метод. Следовательно, Angular может оптимизировать обработку каналов без сохранения состояния / чистых каналов: если их входные данные не меняются, каналы не нужно запускать во время цикла обнаружения изменений. Для канала, такого как {{power | exponentialStrength: factor}}
, power
и factor
являются входами.
Для этого вопроса, "#student of students | sortByName:queryElem.value"
, students
и queryElem.value
являются входами, а труба sortByName
имеет состояние / чистая. students
это массив (ссылка).
- Когда добавляется студент, ссылка на массив не изменяется -
students
не изменяется - следовательно, канал без сохранения состояния / чистый канал не выполняется.
- Когда что-то вводится во вход фильтра,
queryElem.value
действительно изменяется, поэтому выполняется канал без сохранения состояния / чистый.
Один из способов решить проблему с массивом - это изменять ссылку на массив каждый раз, когда добавляется ученик, то есть создавать новый массив каждый раз, когда добавляется ученик. Мы могли бы сделать это с помощью concat()
:
this.students = this.students.concat([{name: studentName}]);
Хотя это работает, наш addNewStudent()
метод не должен быть реализован определенным образом только потому, что мы используем канал. Мы хотим использовать push()
для добавления в наш массив.
Каналы с отслеживанием состояния
У каналов с отслеживанием состояния есть состояние - обычно у них есть свойства, а не только transform()
метод. Их может потребоваться оценка, даже если их входные данные не изменились. Когда мы указываем, что канал имеет состояние / не является чистым - pure: false
тогда всякий раз, когда система обнаружения изменений Angular проверяет компонент на наличие изменений, и этот компонент использует канал с сохранением состояния, он будет проверять вывод канала, изменился ли его вход или нет.
Это похоже на то, что мы хотим, хотя это менее эффективно, поскольку мы хотим, чтобы канал выполнялся, даже если students
ссылка не изменилась. Если мы просто сделаем канал с отслеживанием состояния, мы получим ошибку:
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
Согласно ответу @drewmoore , «эта ошибка возникает только в режиме разработки (который включен по умолчанию с бета-версии 0). Если вы вызовете enableProdMode()
при загрузке приложения, ошибка не будет выдана». В документах дляApplicationRef.tick()
государства:
В режиме разработки tick () также выполняет второй цикл обнаружения изменений, чтобы гарантировать, что дальнейшие изменения не будут обнаружены. Если во время этого второго цикла будут внесены дополнительные изменения, привязки в приложении будут иметь побочные эффекты, которые невозможно устранить за один проход обнаружения изменений. В этом случае Angular выдает ошибку, поскольку приложение Angular может иметь только один проход обнаружения изменений, во время которого должно завершиться обнаружение всех изменений.
В нашем сценарии я считаю ошибку ложной / вводящей в заблуждение. У нас есть конвейер с отслеживанием состояния, и вывод может меняться каждый раз при его вызове - это может иметь побочные эффекты, и это нормально. NgFor оценивается после канала, поэтому он должен работать нормально.
Однако мы не можем развиваться с этой ошибкой, поэтому один способ обхода - добавить свойство массива (то есть состояние) в реализацию конвейера и всегда возвращать этот массив. См. Ответ @ pixelbits для этого решения.
Однако мы можем быть более эффективными, и, как мы увидим, нам не понадобится свойство массива в реализации конвейера, и нам не понадобится обходной путь для обнаружения двойного изменения.
Обнаружение изменения компонентов
По умолчанию при каждом событии браузера обнаружение изменений Angular проходит через каждый компонент, чтобы узнать, изменился ли он - проверяются входные данные и шаблоны (и, возможно, другие вещи?).
Если мы знаем, что компонент зависит только от своих входных свойств (и событий шаблона), и что входные свойства неизменны, мы можем использовать гораздо более эффективную onPush
стратегию обнаружения изменений. В этой стратегии, вместо проверки каждого события браузера, компонент проверяется только при изменении входных данных и при срабатывании событий шаблона. И, видимо, Expression ... has changed after it was checked
с этой настройкой мы не получаем эту ошибку. Это связано с тем, что onPush
компонент не проверяется снова, пока он снова не будет "отмечен" ( ChangeDetectorRef.markForCheck()
). Таким образом, привязки шаблонов и выходы канала с сохранением состояния выполняются / оцениваются только один раз. Каналы без сохранения состояния / чистые каналы по-прежнему не выполняются, если их входные данные не меняются. Так что нам по-прежнему нужен канал с отслеживанием состояния.
Это решение, предложенное @EricMartinez: канал с отслеживанием состояния с onPush
обнаружением изменений. См. Ответ @caffinatedmonkey для этого решения.
Обратите внимание, что с этим решением transform()
методу не нужно каждый раз возвращать один и тот же массив. Я нахожу это немного странным: канал с отслеживанием состояния без состояния. Подумав еще об этом ... канал с отслеживанием состояния, вероятно, всегда должен возвращать один и тот же массив. В противном случае его можно было бы использовать только с onPush
компонентами в режиме разработки.
Итак, после всего этого, я думаю, мне нравится комбинация ответов @ Eric и @ pixelbits: конвейер с отслеживанием состояния, который возвращает одну и ту же ссылку на массив, с onPush
обнаружением изменений, если компонент это позволяет. Поскольку канал с отслеживанием состояния возвращает ту же ссылку на массив, канал по-прежнему можно использовать с компонентами, которые не настроены с помощью onPush
.
Plunker
Вероятно, это станет идиомой Angular 2: если массив передает канал, и массив может измениться (элементы в массиве, а не ссылка на массив), нам нужно использовать канал с отслеживанием состояния.
pure:false
в свой канал иchangeDetection: ChangeDetectionStrategy.OnPush
в свой компонент.