Щоб повністю зрозуміти проблему та можливі рішення, нам потрібно обговорити виявлення кутових змін - для труб та компонентів.
Виявлення зміни труби
Труби без громадянства
За замовчуванням труби без стану / чисті. Труби без стану / чисті труби просто перетворюють вхідні дані у вихідні дані. Вони нічого не пам’ятають, тому не мають жодних властивостей - лише transform()
метод. Таким чином, кутовий може оптимізувати обробку труб без стану / чистого стану: якщо їх вхід не змінюється, труби не потрібно виконувати під час циклу виявлення змін. Для таких труб, як {{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()
держави:
У режимі розробки галочка () також виконує другий цикл виявлення змін, щоб гарантувати, що подальших змін не буде виявлено. Якщо під час цього другого циклу з’являються додаткові зміни, прив’язки в додатку можуть мати побічні ефекти, які неможливо вирішити за один пропуск виявлення змін. У цьому випадку Angular видає помилку, оскільки кутова програма може мати лише один пропуск виявлення змін, під час якого слід виявити всі зміни.
У нашому сценарії я вважаю, що помилка є хибною / оманливою. У нас є потужна труба, і вихід може змінюватися кожного разу, коли він викликається - це може мати побічні ефекти, і це нормально. NgFor оцінюється після труби, тому він повинен працювати добре.
Однак ми не можемо реально розвиватися, коли ця помилка викидається, тому одним вирішенням є додавання властивості масиву (тобто стану) до реалізації труби та завжди повертати цей масив. Дивіться відповідь @ pixelbits для цього рішення.
Однак ми можемо бути ефективнішими, і, як ми побачимо, нам не знадобиться властивість масиву в реалізації труби, і нам не знадобиться рішення для виявлення подвійних змін.
Виявлення зміни компонентів
За замовчуванням у будь-якій події браузера виявлення кутових змін проходить через кожен компонент, щоб побачити, чи змінилось - перевіряються входи та шаблони (а може бути й інші речі?)
Якщо ми знаємо, що компонент залежить тільки від його вхідних властивостей (і подій шаблону), і що властивості введення незмінні, ми можемо використовувати набагато ефективнішу onPush
стратегію виявлення змін. За допомогою цієї стратегії замість перевірки кожної події веб-переглядача компонент перевіряється лише тоді, коли змінюються входи та коли спрацьовують події шаблону. І, мабуть, ми не отримуємо цієї Expression ... has changed after it was checked
помилки з цим налаштуванням. Це тому, що onPush
компонент не перевіряється знову, поки він не буде "позначений" ( ChangeDetectorRef.markForCheck()
) знову. Таким чином, прив'язки шаблонів і стаціонарні виходи труб виконуються / оцінюються лише один раз. Труби без стану / чисті труби досі не виконуються, якщо їх вхід не зміниться. Тож нам ще потрібна велика труба.
Це рішення @EricMartinez запропонував: стаціонарна труба з onPush
виявленням змін. Дивіться відповідь @ caffinedmonkey на це рішення.
Зауважте, що за допомогою цього рішення transform()
методу не потрібно щоразу повертати один і той же масив. Я вважаю це трохи дивним: велика труба, яка не має штату. Якщо подумати про це ще трохи ... стан, який, напевно, повинен завжди повертати той же масив. В іншому випадку його можна використовувати лише з onPush
компонентами в режимі розробки.
Отже, після всього цього, я думаю, що мені подобається поєднання відповідей @ Еріка та @ піксельбіта: стаціонарна труба, яка повертає ту саму посилання масиву, з onPush
виявленням змін, якщо компонент це дозволяє. Оскільки стаціонарна труба повертає ту саму посилання на масив, труба все ще може використовуватися з компонентами, які не налаштовані onPush
.
Plunker
Це, мабуть, стане ідіомою Angular 2: якщо масив подає трубу, і масив може змінитися (елементи масиву, тобто не посилання на масив), нам потрібно використовувати стаціонарну трубу.
pure:false
до своєї труби таchangeDetection: ChangeDetectionStrategy.OnPush
до свого компонента.