NgFor не обновляет данные с Pipe in Angular2
в этом сценарии я показываю список студентов (массив) в представлении с ngFor
:
<li *ngFor="#student of students">{{student.name}}</li>
замечательно, что он обновляется всякий раз, когда я добавляю другого студента в список.
однако, когда я даю ему pipe
to filter
на имя студента
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
он не обновляет список, пока я не введу что-то в поле Имя фильтрационного студента.
вот ссылка на plnkr.
она.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.ТС
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;
}
}
4 ответов
чтобы полностью понять проблему и возможные решения, нам нужно обсудить обнаружение угловых изменений -- для труб и компонентов.
Обнаружение Изменения Трубы
без гражданства / чистые трубы
по умолчанию, трубы без гражданства/чисто. Трубы без состояния / pure просто преобразуют входные данные в выходные данные. Они ничего не помнят, поэтому у них нет никаких свойств – просто transform()
метод. Угловой может поэтому оптимизировать обработку апатридов / pure трубы: если их входные сигналы не изменяют, то трубам не нужно быть исполненным во время цикла обнаружения изменения. Для такой трубы, как {{power | exponentialStrength: factor}}
, power
и factor
входы.
для этого вопроса,"#student of students | sortByName:queryElem.value"
, students
и queryElem.value
входы и трубы sortByName
является лицом без гражданства/чисто. students
массив (ссылка).
- когда студент добавляется, массив ссылка не меняет –
students
не меняет – следовательно, чисто труба без гражданства, не выполненный. - когда что-то вводится во входной фильтр,
queryElem.value
изменяется, следовательно, выполняется канал без состояния / pure.
один из способов исправить проблему массива-изменить ссылку на массив каждый раз, когда добавляется студент, т. е. создать новый массив каждый раз, когда добавляется студент. Мы могли бы сделать это с помощью concat()
:
this.students = this.students.concat([{name: studentName}]);
хотя это работает, наши addNewStudent()
метод не должен быть реализован определенным образом только потому, что мы используем труба. Мы хотим использовать push()
чтобы добавить к нашему массиву.
Трубы Состояния
трубы с состоянием имеют состояние - они обычно имеют свойства, а не только transform()
метод. Возможно, их необходимо оценить, даже если их входные данные не изменились. Когда мы указываем, что труба является stateful / non-pure -pure: false
- тогда всякий раз, когда система обнаружения изменений Angular проверяет компонент на наличие изменений, и этот компонент использует канал с состоянием, он проверяет выход трубы, ввод изменился или нет.
это звучит так, как мы хотим, хотя это менее эффективно, так как мы хотим, чтобы труба выполнялась, даже если students
ссылка не изменилась. Если мы просто сделаем трубу stateful, мы получим ошибку:
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 это, " эта ошибка происходит только в режиме dev (который включен по умолчанию с beta-0). Если вы позвоните enableProdMode()
при загрузке приложения ошибка не будет выброшена." Этот документы на ApplicationRef.tick()
состояние:
в режиме разработки tick () также выполняет второй цикл обнаружения изменений, чтобы убедиться, что никаких дальнейших изменений не обнаружено. Если во время этого второго цикла происходят дополнительные изменения, то привязки в приложении имеют побочные эффекты, которые не могут быть разрешены за один проход обнаружения изменений. В этом случае Angular выдает ошибку, так как приложение Angular может иметь только один проход обнаружения изменений, во время которого все обнаружение изменений должны заполнить.
в нашем сценарии я считаю, что ошибка является фиктивной / вводящей в заблуждение. У нас есть канал с состоянием, и выход может меняться каждый раз, когда он называется – он может иметь побочные эффекты, и это нормально. NgFor оценивается после трубы, поэтому он должен работать нормально.
однако мы не можем действительно развиваться с этой ошибкой, поэтому одним обходным путем является добавление свойства массива (т. е. состояния) в реализацию канала и всегда возвращать этот массив. См. @pixelbits по отвечать за это решение.
однако мы можем быть более эффективными, и, как мы увидим, нам не понадобится свойство array в реализации трубы, и нам не понадобится обходной путь для обнаружения двойного изменения.
Обнаружение Изменения Компонентов
по умолчанию в каждом событии браузера обнаружение угловых изменений проходит через каждый компонент, чтобы увидеть, изменился ли он-входы и шаблоны (и, возможно, другие вещи?) проверяются.
если мы знаем, что компонент зависит только от его входных свойств (и событий шаблона), и что входные свойства неизменяемы, мы можем использовать гораздо более эффективный onPush
изменить стратегию обнаружения. В этой стратегии вместо проверки каждого события браузера компонент проверяется только при изменении входных данных и при запуске событий шаблона. И, по-видимому, мы этого не понимаем Expression ... has changed after it was checked
ошибка с этой настройкой. Это потому что onPush
компонент не проверяется снова, пока он не будет " отмечен" (ChangeDetectorRef.markForCheck()
) еще раз. Таким образом, привязки шаблонов и выходы канала с состоянием выполняются/оцениваются только один раз. Трубы без состояния / pure по-прежнему не выполняются, если их входы не изменяются. Так что нам все еще нужна труба с состоянием.
это решение @EricMartinez предложил: stateful трубы с onPush
обнаружения изменений. См. ответ @caffinatedmonkey для этого решения.
обратите внимание, что с этим решением transform()
метод не должен возвращать один и тот же массив каждый раз. Я найдите это немного странным, хотя: статичная труба без состояния. Подумав об этом еще немного... канал с состоянием, вероятно, всегда должен возвращать один и тот же массив. В противном случае он может использоваться только с onPush
компоненты в режиме dev.
Итак, после всего этого, я думаю, мне нравится комбинация ответов @Eric и @pixelbits: stateful pipe, который возвращает ту же ссылку на массив, с onPush
обнаружение изменений, если компонент позволяет. Поскольку канал stateful возвращает то же самое ссылка на массив, труба все еще может использоваться с компонентами, которые не настроены с onPush
.
это, вероятно, станет угловой идиомой 2: если массив подает канал, и массив может измениться (элементы в массиве, а не Ссылка на массив), нам нужно использовать канал с состоянием.
как отметил Эрик Мартинес в комментариях, добавив pure: false
на Pipe
декоратор и changeDetection: ChangeDetectionStrategy.OnPush
на Component
декоратор исправит вашу проблему. вот рабочая plunkr. изменение ChangeDetectionStrategy.Always
, также работает. Вот почему.
согласно Angular2 руководство по трубам:
трубы по умолчанию не имеют состояния. Мы должны объявить трубу статичной, установив
pure
свойства@Pipe
декораторfalse
. Этот параметр указывает системе обнаружения изменений Angular проверять выход этой трубы каждый цикл, независимо от того, изменился вход или нет.
что касается ChangeDetectionStrategy
по умолчанию, все привязки проверил каждый цикл. Когда pure: false
добавлена труба, я считаю, что метод обнаружения изменений изменяется на from CheckAlways
to CheckOnce
по соображениям производительности. С OnPush
, привязки для компонента проверяются только при изменении входного свойства или когда запускается событие. Для получения дополнительной информации о детекторах изменений, важная часть angular2
, проверьте следующие ссылки:
вам не нужно изменять ChangeDetectionStrategy. Реализация канала с состоянием достаточно, чтобы все работало.
Это канал с состоянием (никаких других изменений не было сделано):
@Pipe({
name: 'sortByName',
pure: false
})
export class SortByNamePipe {
tmp = [];
transform (value, [queryString]) {
this.tmp.length = 0;
// console.log(value, queryString);
var arr = value.filter((student)=>new RegExp(queryString).test(student.name));
for (var i =0; i < arr.length; ++i) {
this.tmp.push(arr[i]);
}
return this.tmp;
}
}
чистые и нечистые трубы
есть две категории труб: чистые и нечистые. Трубы по умолчанию чистые. Каждая труба, которую вы видели до сих пор, была чистой. Вы делаете трубу нечистой, устанавливая ее чистый флаг в false. Вы могли бы сделать FlyingHeroesPipe нечистым, как это:
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
прежде чем делать это, понять разницу между чистым и нечистым, начиная с чистого труба.
чистые трубы Angular выполняет чистую трубу только тогда, когда она обнаруживает чистое изменение входного значения. Чистое изменение - это либо изменение примитивного входного значения (строка, число, логическое значение, символ), либо ссылка на измененный объект (дата, массив, функция, объект).
Angular игнорирует изменения внутри (составных) объектов. Он не вызовет чистый канал, если вы измените входной месяц, добавите к входному массиву или обновите свойство входного объекта.
Это может показаться ограничительный, но и быстрый. Проверка ссылки на объект выполняется быстро-намного быстрее, чем глубокая проверка различий,-поэтому Angular может быстро определить, может ли он пропустить как выполнение канала, так и обновление вида.
по этой причине, чисто труба предпочтительнее, когда вы можете жить со стратегией обнаружения изменений. Когда вы не можете, вы можете использовать нечистую трубу.