ChangeDetectionStrategy.OnPush и Observable.subscribe в Angular 2

Я пытаюсь склонить голову к лучшей практике при использовании Observables рядом с ChangeDetectionStrategy.OnPush.

В примере демонстрируется общий сценарий, требующий показать какое-либо загрузочное сообщение (или, возможно, просто анимацию spinner):

Plnkr здесь

@Component({
  selector: 'my-app',
  template: `Are we loading?: {{loadingMessage}}`,

  // Obviously "Default" will notice the change in `loadingMessage`...
  // changeDetection: ChangeDetectionStrategy.Default

  // But what is best practice when using OnPush?
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class App implements OnInit {

  private loadingMessage = "Wait for it...";

  constructor() {

  }

  ngOnInit() {

    // Pretend we're loading data from a service.
    // This component is only interested in the call success
    Observable.of(true)
      .delay(2000)
      .subscribe(success => {

        if(success){
          console.log('Pretend loading: success!');

          // OnPush won't detect this change
          this.loadingMessage = 'Success!';
        }

      });

  }
}

Я более или менее понимаю требование неизменности с OnPush, и, по крайней мере, для меня это имеет смысл, когда речь идет о фактических данных модели (вероятно, хранится в каком-то магазине).

Итак, у меня есть два вопроса:

  • Почему назначение нового строкового значения 'Success!' не запускает детектор изменений? Что касается неизменности, значение изменилось, правильно?
  • Как должно быть реализовано легкое внутреннее состояние компонента (т.е. loadingMessage) при использовании ChangeDetectionStrategy.OnPush? Если есть несколько лучших практик, пожалуйста, укажите мне в правильном направлении.

Ответ 1

Хороший вопрос. У меня есть две цитаты из Савкина о onPush (так как у Angular.io docs пока нет информации по этой теме):

Структура будет проверять компоненты onPush только при изменении их входов или создании шаблонов компонентов. - ref

При использовании onPush, Angular будет проверять компонент только тогда, когда какое-либо из его свойств ввода изменяется, когда оно запускает событие или когда наблюдаемый запускает событие. - ref (в ответе на комментарий к @vivainio)

Вторая цитата кажется более полной. (Слишком плохо, что он был похоронен в комментарии!)

Почему назначение нового строкового значения Success! не запускает датчик изменения? Что касается неизменности, значение изменилось, правильно?

onPush неизменяемость относится к свойствам ввода, а не к нормальным свойствам экземпляра. Если loadingMessage было входным свойством и значение изменилось, обнаружение изменений будет выполняться на компоненте. (См. Ответ @Vlado для Plunker.)

Как должно быть реализовано легкое внутреннее состояние компонента (т.е. loadingMessage) при использовании ChangeDetectionStrategy.OnPush? Если есть несколько лучших практик, пожалуйста, укажите мне в правильном направлении.

Вот что я знаю до сих пор:

  • Если мы изменим внутреннее состояние как часть обработки события или часть наблюдаемого срабатывания, обнаружение изменения выполняется в компоненте onPush (т.е. представление будет обновляться). В вашем конкретном случае я был удивлен, что представление не обновлялось. Вот два догадки о том, почему нет:
    • Добавление delay() делает Angular более похожим на setTimeout(), чем на наблюдаемое изменение. setTimeout не приводят к выполнению обнаружения изменений в компоненте onPush. В вашем примере детектор изменений завершил свою работу за 2 секунды до изменения значения loadingMessage.
    • Как @Sasxa показывает в своем ответе, вы должны иметь привязку в шаблоне к Observable. Я., возможно, это больше, чем просто "наблюдаемые огни"... возможно, это должно быть наблюдаемое связанное, которое срабатывает. В этом случае создание loadingMessage (или даже более общее свойство state) в качестве самого Observable позволит вам привязать ваш шаблон к его значению (или нескольким значениям асинхронности), см. этот пример plnkr.
      Обновление 2016-04-28: Кажется, что привязка должна включать | async, как показано в plnkr, и в this plnkr.
  • Если мы изменим внутреннее состояние и не являемся частью обработки событий или наблюдаемого обжига, мы можем ввести ChangeDetectorRef в наш компонент и метод вызова markForCheck(), чтобы вызвать обнаружение изменений для компонента onPush и все компоненты предка до корневого компонента. Если изменяется только состояние просмотра (то есть состояние, которое является локальным для компонента и, возможно, его потомков), вместо него может использоваться detectChanges(), который не будет отмечать все компоненты-предки для обнаружения изменений. Plunker

Ответ 2

AFAIK, OnPush используется при работе непосредственно с наблюдаемыми:

//our root app component
import {Component, OnInit, ChangeDetectionStrategy} from 'angular2/core'
import {Observable} from 'rxjs/Observable';
import 'rxjs/Rx';

@Component({
  selector: 'my-app',
  template: `Are we loading?: {{ loadingMessage |async }}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class App implements OnInit {
  private loadingMessage;
  constructor() { }

  ngOnInit() {
    this.loadingMessage = Observable.of(true)
      .delay(2000)
  }
}

Ответ 3

С помощью этого ChangeDetectionStrategy.OnPush вы сообщаете своему компоненту только прослушивать изменения в его свойствах ввода.

Я добавил компонент загрузки к вашему примеру, чтобы показать вам, как он работает: http://plnkr.co/edit/k5tkmcLlzkwz4t5ifqFD?p=preview