В этом сценарии я показываю в представлении список студентов (массив) с помощью ngFor
:
<li *ngFor="#student of students">{{student.name}}</li>
Замечательно, что он обновляется всякий раз, когда я добавляю другого ученика в список.
Однако, когда я даю ему , pipe
чтобы filter
по имени студента,
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
Список не обновляется до тех пор, пока я не введу что-нибудь в поле имени учащегося.
Вот ссылка на plnkr .
Hello_world.html
<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>
sort_by_name_pipe.ts
import {Pipe} from 'angular2/core';
@Pipe({
name: 'sortByName'
})
export class SortByNamePipe {
transform(value, [queryString]) {
// console.log(value, queryString);
return value.filter((student) => new RegExp(queryString).test(student.name))
// return value;
}
}
pure:false
в свой канал иchangeDetection: ChangeDetectionStrategy.OnPush
в свой компонент..test()
в вашей функции фильтра. Это потому, что если пользователь вводит строку, которая включает символы специального значения, такие как:*
или+
т.д., ваш код сломается. Я думаю, вам следует использовать.includes()
или избегать строки запроса с помощью настраиваемой функции.pure:false
и изменение состояния канала решит проблему. Изменять ChangeDetectionStrategy не нужно.Ответы:
Чтобы полностью понять проблему и возможные решения, нам нужно обсудить обнаружение изменений Angular - для каналов и компонентов.
Обнаружение замены трубы
Каналы без сохранения состояния / чистые
По умолчанию каналы не имеют состояния / чистые. Каналы без сохранения состояния / чистые каналы просто преобразуют входные данные в выходные данные. Они ничего не помнят, поэтому у них нет никаких свойств - только
transform()
метод. Следовательно, Angular может оптимизировать обработку каналов без сохранения состояния / чистых каналов: если их входные данные не меняются, каналы не нужно запускать во время цикла обнаружения изменений. Для канала, такого как{{power | exponentialStrength: factor}}
,power
иfactor
являются входами.Для этого вопроса,
"#student of students | sortByName:queryElem.value"
,students
иqueryElem.value
являются входами, а трубаsortByName
имеет состояние / чистая.students
это массив (ссылка).students
не изменяется - следовательно, канал без сохранения состояния / чистый канал не выполняется.queryElem.value
действительно изменяется, поэтому выполняется канал без сохранения состояния / чистый.Один из способов решить проблему с массивом - это изменять ссылку на массив каждый раз, когда добавляется ученик, то есть создавать новый массив каждый раз, когда добавляется ученик. Мы могли бы сделать это с помощью
concat()
:Хотя это работает, наш
addNewStudent()
метод не должен быть реализован определенным образом только потому, что мы используем канал. Мы хотим использоватьpush()
для добавления в наш массив.Каналы с отслеживанием состояния
У каналов с отслеживанием состояния есть состояние - обычно у них есть свойства, а не только
transform()
метод. Их может потребоваться оценка, даже если их входные данные не изменились. Когда мы указываем, что канал имеет состояние / не является чистым -pure: false
тогда всякий раз, когда система обнаружения изменений Angular проверяет компонент на наличие изменений, и этот компонент использует канал с сохранением состояния, он будет проверять вывод канала, изменился ли его вход или нет.Это похоже на то, что мы хотим, хотя это менее эффективно, поскольку мы хотим, чтобы канал выполнялся, даже если
students
ссылка не изменилась. Если мы просто сделаем канал с отслеживанием состояния, мы получим ошибку:Согласно ответу @drewmoore , «эта ошибка возникает только в режиме разработки (который включен по умолчанию с бета-версии 0). Если вы вызовете
enableProdMode()
при загрузке приложения, ошибка не будет выдана». В документах дляApplicationRef.tick()
государства:В нашем сценарии я считаю ошибку ложной / вводящей в заблуждение. У нас есть конвейер с отслеживанием состояния, и вывод может меняться каждый раз при его вызове - это может иметь побочные эффекты, и это нормально. 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: если массив передает канал, и массив может измениться (элементы в массиве, а не ссылка на массив), нам нужно использовать канал с отслеживанием состояния.
источник
onPush
обнаружения изменений plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=previewКак отметил Эрик Мартинес в комментариях, добавление
pure: false
в вашPipe
декоратор иchangeDetection: ChangeDetectionStrategy.OnPush
в вашComponent
декоратор решит вашу проблему. Вот рабочий плункр. Переход наChangeDetectionStrategy.Always
, тоже работает. Вот почему.Согласно руководству angular2 по трубам :
Что касается
ChangeDetectionStrategy
, по умолчанию, все привязки проверяются каждый цикл. Приpure: false
добавлении трубы, я считаю , что метод обнаружения изменений меняется отCheckAlways
доCheckOnce
по соображениям производительности. СOnPush
привязки для Компонента проверяются только при изменении свойства ввода или при запуске события. Для получения дополнительной информации о детекторах изменений, важной частиangular2
, перейдите по следующим ссылкам:источник
pure: false
добавляется канал, я считаю, что метод обнаружения изменений меняется с CheckAlways на CheckOnce» - это не согласуется с тем, что вы процитировали из документов. Насколько я понимаю, входные данные канала без сохранения состояния по умолчанию проверяются каждый цикл. Если есть изменение,transform()
вызывается метод канала для (повторного) генерации вывода.transform()
Метод канала с отслеживанием состояния вызывается каждый цикл, и его выходные данные проверяются на наличие изменений. См. Также stackoverflow.com/a/34477111/215945 .OnPush
(т. Е. Компонент помечен как «неизменяемый»), выходной канал с отслеживанием состояния не должен «стабилизироваться» - т. Е. Кажется, чтоtransform()
метод запускается только один раз (возможно, все обнаружение изменений для компонента запускается только один раз). Сравните это с ответом @ pixelbits, гдеtransform()
метод вызывается несколько раз, и он должен стабилизироваться (согласно другому ответу pixelbits ), следовательно, необходимо использовать одну и ту же ссылку на массив.transform
вызывается только тогда, когда новые данные отправляются, а не несколько раз, пока вывод не стабилизируется, верно?Демо Plunkr
Вам не нужно изменять ChangeDetectionStrategy. Реализации канала с отслеживанием состояния достаточно, чтобы все заработало.
Это канал с отслеживанием состояния (других изменений не было):
источник
Expression 'students | sortByName:queryElem.value in HelloWorld@7:6' has changed after it was checked. Previous value: '[object Object],[object Object],[object Object]'. Current value: '[object Object],[object Object],[object Object]' in [students | sortByName:queryElem.value in HelloWorld@7:6]
Из документации angular
Чистые и нечистые трубы
Есть две категории труб: чистые и нечистые. Трубы по умолчанию чистые. Каждая трубка, которую вы видели до сих пор, была чистой. Вы делаете канал нечистым, устанавливая его чистый флаг в false. Вы можете сделать FlyingHeroesPipe нечистым следующим образом:
@Pipe({ name: 'flyingHeroesImpure', pure: false })
Прежде чем это сделать, разберитесь в разнице между чистым и нечистым, начиная с чистой трубы.
Чистые каналы Angular выполняет чистый канал только тогда, когда обнаруживает чистое изменение входного значения. Чистое изменение - это либо изменение примитивного входного значения (String, Number, Boolean, Symbol), либо измененная ссылка на объект (Date, Array, Function, Object).
Angular игнорирует изменения внутри (составных) объектов. Он не будет вызывать чистый канал, если вы измените входной месяц, добавите во входной массив или обновите свойство входного объекта.
Это может показаться ограничительным, но это также быстро. Проверка ссылки на объект выполняется быстро - намного быстрее, чем глубокая проверка различий, - поэтому Angular может быстро определить, может ли он пропустить как выполнение конвейера, так и обновление представления.
По этой причине предпочтительнее использовать чистый канал, если вы можете жить со стратегией обнаружения изменений. Когда нет, можно использовать нечистую трубку.
источник
Вместо того, чтобы делать чисто: ложь. Вы можете глубоко скопировать и заменить значение в компоненте на this.students = Object.assign ([], NEW_ARRAY); где NEW_ARRAY - модифицированный массив.
Он работает для angular 6 и должен работать и для других угловых версий.
источник
Обходной путь: вручную импортируйте канал в конструктор и вызовите метод преобразования с помощью этого канала.
На самом деле тебе даже труба не нужна
источник
Добавьте в канал дополнительный параметр и измените его сразу после изменения массива, и даже с чистым каналом список будет обновлен
пусть пункт пунктов | pipe: param
источник
В этом случае я использовал свой канал в ts-файле для фильтрации данных. Это намного лучше с точки зрения производительности, чем использование чистых труб. Используйте в ts так:
источник
создание нечистых труб обходится дорого. поэтому не создавайте нечистые каналы, а лучше измените ссылку на переменную данных, создав копию данных при изменении данных и переназначив ссылку копии в исходной переменной данных.
источник