Angular 2: Как обнаружить изменения в массиве? (свойство @input)

У меня есть родительский компонент, который извлекает массив объектов, используя запрос ajax.

Этот компонент имеет два дочерних компонента: один из них показывает объекты в древовидной структуре, а другой - его содержимое в формате таблицы. Родитель передает массив своим дочерним элементам через свойство @input, и они правильно отображают содержимое. Все, как ожидалось.

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

Я привык работать с Knockout JS, и мне нужно получить эффект, похожий на эффект наблюдаемых массивов.

Я прочитал кое-что о DoCheck, но я не уверен, как это работает.

Ответ 1

OnChanges Lifecycle Hook будет срабатывать только при изменении экземпляра свойства свойства.

Если вы хотите проверить, был ли добавлен, перемещен или удален элемент внутри входного массива, вы можете использовать IterableDiffers внутри DoCheck Lifecycle Hook следующим образом:

constructor(private _iterableDiffers: IterableDiffers) {
    this.iterableDiffer = this._iterableDiffers.find([]).create(null);
}

ngDoCheck() {
    let changes = this.iterableDiffer.diff(this.inputArray);
    if (changes) {
        console.log('Changes detected!');
    }
}

Если вам нужно обнаружить изменения в объектах внутри массива, вам нужно будет перебирать все элементы и применять KeyValueDiffers для каждого элемент. (Вы можете сделать это параллельно с предыдущей проверкой).

Посетите это сообщение для получения дополнительной информации: Обнаружение изменений в объектах внутри массива в Angular2

Ответ 2

Вы всегда можете создать новую ссылку на массив, объединив его с пустым массивом:

this.yourArray = [{...}, {...}, {...}];
this.yourArray[0].yourModifiedField = "whatever";

this.yourArray = [].concat(this.yourArray);

Приведенный выше код изменит ссылку на массив и запустит механизм OnChanges в дочерних компонентах.

Ответ 3

Прочитайте следующую статью, не пропустите изменяемые и неизменяемые объекты.

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

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

https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

Ответ 4

Вы можете использовать IterableDiffers

Используется * ngFor

constructor(private _differs: IterableDiffers) {}

ngOnChanges(changes: SimpleChanges): void {
  if (!this._differ && value) {
     this._differ = this._differs.find(value).create(this.ngForTrackBy);
  }
}

ngDoCheck(): void {
  if (this._differ) {
    const changes = this._differ.diff(this.ngForOf);
    if (changes) this._applyChanges(changes);
  }
}

Ответ 5

Это уже кажется ответом. Однако для будущих лиц, ищущих проблемы, я хотел добавить что-то упущенное, когда я искал и устранял проблему обнаружения изменений, которая у меня была. Теперь моя проблема была немного изолированной и, по общему признанию, глупой ошибкой с моей стороны, но тем не менее актуальной. Когда вы обновляете значения в Array или Object в качестве ссылки, убедитесь, что вы находитесь в правильной области видимости. Я попал в ловушку с помощью setInterval(myService.function, 1000), где myService.function() будет обновлять значения открытого массива, который я использовал вне службы. Это никогда не обновляло массив, так как привязка была отключена, и правильное использование должно было быть setInterval(myService.function.bind(this), 1000). Я потратил впустую свое время, пытаясь взломать обнаружение изменений, когда это была глупая/простая ошибка. Исключите область применения как виновника, прежде чем пытаться найти решения для обнаружения изменений; это может сэкономить вам время.

Ответ 6

Вы можете использовать нечистый канал, если вы непосредственно используете массив в шаблоне компонентов. (Этот пример для простых массивов, которые не нуждаются в глубокой проверке)

@Pipe({
  name: 'arrayChangeDetector',
  pure: false
})
export class ArrayChangeDetectorPipe implements PipeTransform {
  private differ: IterableDiffer<any>;

  constructor(iDiff: IterableDiffers) {
    this.differ = iDiff.find([]).create();
  }

  transform(value: any[]): any[] {
    if (this.differ.diff(value)) {
      return [...value];
    }
    return value;
  }
}
<cmp [items]="arrayInput | arrayChangeDetector"></cmp>

Для тех путешественников во времени, которые по-прежнему сталкиваются с множеством проблем, здесь воспроизведена проблема вместе с несколькими возможными решениями.

https://stackblitz.com/edit/array-value-changes-not-detected-ang-8

Решения включают в себя:

  • NgDoCheck
  • Использование pipes
  • Использование неизменного JS NPM github

Ответ 7

Это работает для меня:

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})
export class MyComponent implements DoCheck {

  @Input() changeArray: MyClassArray[]= [];
  private differ: IterableDiffers;

  constructor(private differs: IterableDiffers) {
    this.differ = differs;
  }

  ngDoCheck() {
    const changes = this.differ.find(this.insertedTasks);
    if (changes) {
      this.myMethodAfterChange();
  }
}