Angular2 ngFor Обнаружение изменения OnPush с мутациями массива

У меня есть компонент таблицы данных (angular2-data-table), где мы изменили проект из Angular традиционного обнаружения изменений на OnPush для оптимизации скорости рендеринга.

После того, как была реализована новая стратегия обнаружения изменений, была отмечена ошибка, ссылающаяся на таблицу, не обновляющуюся, когда объект данных мутирован, например, обновления свойств объекта. Ссылка: https://github.com/swimlane/angular2-data-table/issues/255. Мощный прецедент может быть применен для такого типа потребностей для таких вещей, как встроенное редактирование или внешние изменения данных, к одному свойству в большом сборе данных, таком как тикер акций.

Чтобы решить проблему, мы добавили настраиваемую проверку свойств trackBy с именем trackByProp. Ссылка: commit. К сожалению, это решение не решило проблему.

На демонстрационной странице при живой перезагрузке вы можете увидеть демонстрацию, на которую ссылаются в приведенной выше операции фиксации, но не обновлять таблицу до тех пор, пока вы не нажмете на нее обнаружение изменений.

Структура компонента - это что-то вроде:

Table > Body > Row Group > Row > Cell

все эти компоненты реализуют OnPush. Я использую геттеры/сеттеры в наборе строк для запуска перерасчета страниц, как показано здесь.

Мы хотим остаться с обнаружением изменений OnPush для тех, кто реализует этот шаблон, однако, как проект с открытым исходным кодом с несколькими потребителями, можно утверждать, что какая-то пользовательская функция проверки для видимых значений строк на экране.

Все, что сказано, trackBy не запускает обнаружение изменений в значениях ячеек строки, каков наилучший способ для этого?

Ответ 1

Angular2 обнаружение изменений не проверяет содержимое массивов или объектов.

Хакерное обходное решение состоит в том, чтобы просто создать копию массива после мутации

this.myArray.push(newItem);
this.myArray = this.myArray.slice();

Таким образом, this.myArray ссылается на другой экземпляр массива, и Angular распознает изменение.

Другим подходом является использование IterableDiffer (для массивов) или KeyValueDiffer (для объектов)

// inject a differ implementation 
constructor(differs: KeyValueDiffers) {
  // store the initial value to compare with
  this.differ = differs.find({}).create(null);
}

@Input() data: any;

ngDoCheck() {
  var changes = this.differ.diff(this.data); // check for changes
  if (changes && this.initialized) {
    // do something if changes were found
  }
}

См. также https://github.com/angular/angular/blob/14ee75924b6ae770115f7f260d720efa8bfb576a/modules/%40angular/common/src/directives/ng_class.ts#L122

Ответ 2

Возможно, вы захотите использовать метод markForCheck из ChangeDetectorRef.


У меня есть аналогичная проблема, когда у меня есть компонент, который содержит много данных и перепроверяет их все в каждом цикле обнаружения изменений, не является вариантом. Но поскольку мы наблюдаем некоторые свойства из URL-адреса, и мы меняем вещи в представлении соответственно, onPush наш взгляд не обновляется (автоматически).

Итак, в вашем конструкторе используйте DI, чтобы получить экземпляр ChangeDetectorRef: constructor(private changeDetectorRef: ChangeDetectorRef)

И везде, где вам нужно вызвать changeDetection: this.changeDetectorRef.markForCheck();

Ответ 3

Я тоже столкнулся с аналогичной проблемой, где для оптимизации производительности моего приложения мне пришлось использовать стратегию changeDetection.OnPush. Поэтому я ввел его как в родительский компонент, так и в мой дочерний компонент-конструктор, экземпляр changeDetectorRef

    export class Parentcomponent{
       prop1;

       constructor(private _cd : ChangeDetectorRef){
          }
       makeXHRCall(){
        prop1 = ....something new value with new reference;
        this._cd.markForCheck(); // To force angular to trigger its change detection now
       }
    }

Аналогично в дочернем компоненте, инклинированный экземпляр changeDetectorRef

    export class ChildComponent{
     @Input myData: myData[];
     constructor(private _cd : ChangeDetectorRef){
          }
       changeInputVal(){
        this.myData = ....something new value with new reference;
        this._cd.markForCheck(); // To force angular to trigger its change detection now
       }
    }

Angular change Обнаружение срабатывает при каждой асинхронной функции: -

  • любое событие DOM, например, щелчок, отправка, наведение мыши.
  • любой вызов XHR
  • любые таймеры, такие как setTimeout() и т.д.

Итак, этот вид замедляет приложение, потому что даже когда мы перетаскиваем мышь, angular запускает changeDetection. Для сложного приложения, охватывающего несколько компонентов, это может быть одним из основных узких мест в производительности, поскольку angular имеет эту стратегию поиска родительского дерева для дерева. Чтобы этого избежать, лучше использовать стратегию OnPush и принудительно запускать обнаружение изменений angular, где мы видим, что есть изменение ссылки.

Во-вторых, в стратегии OnPush нужно быть очень осторожным, чтобы он вызывал только изменение при изменении ссылки на объект, а не только значение свойства объекта, то есть в angular change "означает" новая ссылка ".

Например: -

    obj = { a:'value1', b:'value2'}'
    obj.a = value3;

Значение свойства 'a' в 'obj' может иметь изменение, но obj все еще указывает на одну и ту же ссылку, поэтому angular обнаружение изменений не будет запускаться здесь (если вы не заставите его); Чтобы создать новую ссылку, нужно клонировать объект в другой объект и соответствующим образом назначать его свойства.

для дальнейшего понимания, прочитайте о Immmutable Data structure, измените Detection здесь

Ответ 4

Поздний ответ, но другой обходной путь - использование оператора распространения после мутации.. myArr = [...myArr] или myObj = {...myObj}

Это может быть сделано даже при мутировании: myArr = myMutatingArr([...myArr]) поскольку параметр берется в качестве новой ссылки на массив, в результате чего переменная принимает новую ссылку, и поэтому вызывается проверка Angular.

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

Будьте осторожны, поскольку вложенные структуры данных внутри структур данных требуют изменения ссылки до уровня вложенности. Вы должны будете сделать итерацию, которая возвращает спред внутри спреда, как таковой:

myObj = {...myObj, propToChange: {...myObj.propToChange, nestedPropArr: [...myObj.propToChange.nestedPropArr ] } }

что может стать сложным, если вам нужна итерация по объектам и тому подобное. Надеюсь, это поможет кому-то!