NgFor не обновляет данные с помощью Pipe в Angular2

В этом сценарии я отображаю список студентов (массив) для просмотра с помощью ngFor:

<li *ngFor="#student of students">{{student.name}}</li>

Это замечательно, что он обновляется всякий раз, когда я добавляю другого ученика в список.

Однако, когда я даю ему pipe для filter по имени студента,

<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>

Он не обновляет список до тех пор, пока я не введу что-то в поле имени студента.

Здесь ссылка на плнкр.

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;
    }
}

Ответ 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.

Plunker

Это, вероятно, станет идиомой Angular 2: если массив подает канал, и массив может измениться (элементы в массиве, а не ссылка на массив), нам нужно использовать канал с состоянием.

Ответ 2

Как отметил Эрик Мартинес в комментариях, добавление pure: false в ваш декоратор Pipe и changeDetection: ChangeDetectionStrategy.OnPush к вашему декоратору Component устранит вашу проблему. Вот рабочий plunkr. Смена на ChangeDetectionStrategy.Always также работает. Вот почему.

В соответствии с руководством angular2 на трубах:

Трубы по умолчанию не имеют апатридов. Мы должны объявить, что труба должна быть сдержанной, установив свойство pure декоратора @Pipe на false. Этот параметр сообщает системе обнаружения изменений Angular s, чтобы проверить выход этого канала на каждый цикл, изменился ли его вход или нет.

Что касается ChangeDetectionStrategy, по умолчанию все привязки проверяются каждый отдельный цикл. Когда добавлен канал pure: false, я считаю, что метод обнаружения изменений изменяется с CheckAlways до CheckOnce по соображениям производительности. С помощью OnPush привязки для компонента проверяются только при изменении свойства ввода или при срабатывании события. Для получения дополнительной информации о детекторах изменений, важной части angular2, проверьте следующие ссылки:

Ответ 3

Demo Plunkr

Вам не нужно менять 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;
  }
}

Ответ 4

Из угловой документации

Чистые и нечистые трубы

Есть две категории труб: чистые и нечистые. Трубы чистые по умолчанию. Каждая труба, которую вы видели до сих пор, была чистой. Вы делаете трубу нечистой, устанавливая ее чистый флаг в false. Вы можете сделать FlyingHeroesPipe нечистым вот так:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

Прежде чем делать это, поймите разницу между чистым и нечистым, начиная с чистой трубы.

Чистые каналы Angular выполняет чистые каналы только тогда, когда обнаруживает чистое изменение входного значения. Чистое изменение - это либо изменение примитивного входного значения (String, Number, Boolean, Symbol), либо изменение ссылки на объект (Date, Array, Function, Object).

Angular игнорирует изменения внутри (составных) объектов. Он не будет вызывать чистый канал, если вы измените входной месяц, добавите к входному массиву или обновите свойство входного объекта.

Это может показаться ограничительным, но это также быстро. Проверка ссылки на объект выполняется быстро - намного быстрее, чем глубокая проверка на различия - поэтому Angular может быстро определить, может ли он пропускать как выполнение канала, так и обновление представления.

По этой причине чистый канал предпочтительнее, когда вы можете жить со стратегией обнаружения изменений. Когда вы не можете, вы можете использовать нечистую трубу.

Ответ 5

Обходной путь: вручную импортируйте Pipe в конструктор и вызывайте метод transform, используя этот канал

constructor(
private searchFilter : TableFilterPipe) { }

onChange() {
   this.data = this.searchFilter.transform(this.sourceData, this.searchText)}

На самом деле вам даже не нужна труба

Ответ 6

Вместо того, чтобы делать чистый: ложь. Вы можете глубоко копировать и заменить значение в компоненте this.students = Object.assign([], NEW_ARRAY); где NEW_ARRAY - модифицированный массив.

Он работает для угловых 6 и должен работать и для других угловых версий.

Ответ 7

Добавьте в трубу дополнительный параметр и измените его сразу после изменения массива, и даже при использовании чистого канала список будет обновлен

пусть предмет из предметов | Труба: пары

Ответ 8

В этом случае я использовал мой файл Pipe in ts для фильтрации данных. Это намного лучше для производительности, чем использование чистых pipe. Используйте в тс как это:

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

  YourFunction(value) {
    this.pipe.transform(value, 'pipeFilter');
  }
}