Подтвердить что ты не робот

Angular 2 обнаружения изменений и ChangeDetectionStrategy.OnPush

Я пытаюсь понять механизм ChangeDetectionStrategy.OnPush.

То, что я собираю из своих чтений, заключается в том, что обнаружение изменений работает, сравнивая старое значение с новым значением. Это сравнение вернет false, если ссылка на объект не изменилась.

Однако, похоже, существуют определенные сценарии, в которых это "правило" обходит. Не могли бы вы объяснить, как все это работает?

4b9b3361

Ответ 1

Изменить: Вот плункер, который я сделал, который иллюстрирует обнаружение изменений с помощью стратегии OnPush. Нажмите "привет" , как на 3 уровня, затем попробуйте другие уровни.

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

Изменения происходят из событий

У компонента могут быть поля. Эти поля меняются только после какого-то события, и только после этого.

Мы можем определить событие как щелчок мыши, ajax-запрос, setTimeout...

Потоки данных сверху вниз

Angular поток данных - это односторонняя улица. Это означает, что данные не передаются от детей к родителям. Только от родителя к детям, например, через тег @Input. Единственный способ сделать верхний компонент осведомленным о некоторых изменениях в дочернем элементе - через событие. Это подводит нас к:

Обнаружение изменения триггера события

Когда происходит событие, среда angular проверяет каждый компонент сверху вниз, чтобы узнать, были ли они изменены. Если какой-либо из них изменился, он соответственно обновляет представление.

Angular проверяет все компоненты после того, как событие было запущено. Скажем, у вас есть событие click на компоненте, который является компонентом на самом низком уровне, то есть у него есть родители, но нет детей. Этот клик может инициировать изменение в родительском компоненте с помощью излучателя события, службы и т.д. angular не знает, изменятся ли родители или нет. Вот почему angular проверяет все компоненты после того, как событие было запущено по умолчанию.

Чтобы узнать, изменили ли они angular класс ChangeDetector.

Измените детектор

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

Например, если у нас есть ParentComponent:

@Component({
  selector: 'comp-parent',
  template:'<comp-child [name]="name"></comp-child>'
})
class ParentComponent{
  name:string;
} 

У нас будет детектор изменений, прикрепленный к ParentComponent, который выглядит следующим образом:

class ParentComponentChangeDetector{
    oldName:string; // saves the old state of the component.

    isChanged(newName){
      if(this.oldName !== newName)
          return true;
      else
          return false;
    }
}

Изменение свойств объекта

Как вы могли заметить, метод isChanged вернет false, если вы измените свойство объекта. Действительно

let prop = {name:"cat"};
let oldProp = prop;
//change prop
prop.name = "dog";
oldProp === prop; //true

Поскольку, когда свойство объекта может меняться без возврата true в ChangeDetector isChanged(), angular будет считать, что каждый ниже компонент мог бы также измениться. Поэтому он просто проверяет обнаружение изменений во всех компонентах.

Пример: Здесь мы имеем компонент с субкомпонентом. Хотя обнаружение изменений вернет false для родительского компонента, представление ребенка должно быть хорошо обновлено.

@Component({
  selector: 'parent-comp',
  template: `
    <div class="orange" (click)="person.name='frank'">
      <sub-comp [person]="person"></sub-comp>
    </div>
  `
})
export class ParentComponent {
  person:Person = { name: "thierry" };     
}

// sub component
@Component({
  selector: 'sub-comp',
  template: `
    <div>
      {{person.name}}
    </div>
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

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

Стратегия OnPush

Когда компонент помечен changeDetection: ChangeDetectionStrategy.OnPush, angular будет считать, что входной объект не изменился, если ссылка на объект не изменилась. Это означает, что изменение свойства не приведет к обнаружению изменений. Таким образом, представление будет несовместимо с моделью.

Пример

Этот пример классный, потому что он показывает это в действии. У вас есть родительский компонент, который при нажатии изменит свойства имени входного объекта. Если вы проверите метод click() внутри родительского компонента, вы заметите, что он выводит свойство дочернего компонента в консоли. Это свойство изменилось. Но вы не видите его визуально. Это потому, что представление не обновлено. Из-за стратегии OnPush процесс обнаружения изменений не произошел, потому что объект ref не изменился.

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" (click)="click()">
      <sub-comp [person]="person" #sub></sub-comp>
    </div>
  `
})
export class App {
  person:Person = { name: "thierry" };
  @ViewChild("sub") sub;

  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }
}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>
      {{person.name}}
    </div>
  `
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

export interface Person{
  name:string,
}

После щелчка имя по-прежнему остается в представлении, но не в самом компоненте


Событие, запущенное внутри компонента, вызовет обнаружение изменений.

Здесь мы приходим к тому, что меня смущает в моем первоначальном вопросе. Компонент ниже отмечен стратегией OnPush, но представление обновляется, когда оно изменяется.

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" >
      <sub-comp ></sub-comp>
    </div>
  `,
  styles:[`
    .orange{ background:orange; width:250px; height:250px;}
  `]
})
export class App {
  person:Person = { name: "thierry" };      
  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }

}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="grey" (click)="click()">
      {{person.name}}
    </div>
  `,
  styles:[`
    .grey{ background:#ccc; width:100px; height:100px;}
  `]
})
export class SubComponent{
  @Input()
  person:Person = { name:"jhon" };
  click(){
    this.person.name = "mich";
  }
}

Итак, мы видим, что вход объекта не изменил ссылку, и мы используем стратегию OnPush. Что может заставить нас поверить, что оно не будет обновляться. Фактически он обновляется.

Как сказал Гюнтер в своем ответе, это связано с тем, что с помощью стратегии OnPush обнаружение изменений происходит для компонента, если:

  • полученное связанное событие (щелчок) по самому компоненту.
  • обновлен @Input() (как в файле ref obj)
  • | асинхронная труба получила событие
  • обнаружение изменений было вызвано "вручную"

независимо от стратегии.

Ссылки

Ответ 2

*ngFor имеет ли он собственное обнаружение изменений. Каждый раз, когда выполняется обнаружение изменений, NgFor получает свой метод ngDoCheck(), и там NgFor проверяет, изменилось ли содержимое массива.

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

<button (click)="persons.push({name: 'dynamically added', id: persons.length})">add</button>

тогда щелчок фактически вызовет изменение, которое NgFor должно распознать.

С ChangeDetectionStrategy.OnPush обнаружение изменений в вашем компоненте будет запущено, потому что при обнаружении изменений OnPush выполняется

  • получено связанное событие (click)
  • a @Input() был обновлен с помощью обнаружения изменений
  • | async труба получила событие
  • обнаружение изменений было вызвано "вручную"

Ответ 3

Чтобы предотвратить Application.tick попытаться отключить changeDetector:

constructor(private cd: ChangeDetectorRef) {

ngAfterViewInit() {
  this.cd.detach();
}

Plunker