Угловое обнаружение изменений 2 и ChangeDetectionStrategy.OnPush

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

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

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

4 ответа

Решение

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

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

Компонент может иметь поля. Эти поля изменяются только после какого-то события и только после этого.

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

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

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

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

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

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 в changeDetectorisChanged(), 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 изменилось)
  • | асинхронный канал получил событие
  • обнаружение изменений было вызвано "вручную"

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

связи

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

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

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

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

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

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

Предотвращать Application.tick попробуй отсоединить changeDetector:

constructor(private cd: ChangeDetectorRef) {

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

Plunker

В угловых мы очень используем Parent - дочернюю структуру. Там мы передаем данные формы parent для потомка, используя @Inputs.

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

Но в большинстве ситуаций нам нужно обновлять представление дочернего элемента (вызывать изменение обнаружения) только при изменении его входных данных. Для достижения этого мы можем использовать OnPush ChangeDetectionStrategy и изменять входные данные (используя неизменяемые) по мере необходимости. ССЫЛКА НА САЙТ

По умолчанию каждый раз, когда что-то изменяется (все события браузера, XHR, обещания, таймеры, интервалы и т. Д.) В приложении, Angular запускает обнаружение изменений для каждого компонента, что является дорогостоящим. Когда приложение становится большим, это может вызвать проблемы с производительностью.

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

- The Input reference changes(Immutable inputs)
- An event originated from the component or one of its children
- Run change detection explicitly
- Use the async pipe in the view

Теперь можно спросить, почему Angular не смог сделать onPush стратегией по умолчанию. Ответ: Angular не хочет заставлять вас использовать неизменяемые входные данные.

Другие вопросы по тегам