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

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

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

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

Структура компонента примерно такая:

Table > Body > Row Group > Row > Cell

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

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

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

14
amcdnl 27 Ноя 2016 в 17:21

4 ответа

Лучший ответ

Обнаружение изменений 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

24
Günter Zöchbauer 4 Дек 2016 в 17:52

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


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

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

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

3
maxime1992 27 Ноя 2016 в 14:34

Я тоже столкнулся с аналогичной проблемой, когда для оптимизации производительности моего приложения мне пришлось использовать стратегию 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
       }
    }

Обнаружение углового изменения запускается для каждой асинхронной функции: -

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

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

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

Например:

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

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

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

2
Gaurav Pandvia 23 Апр 2017 в 09:40

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

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

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

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

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

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

0
Vogel612 7 Июн 2020 в 19:58