Angular - Rx JS: afterViewInit и Asyn c pipe - PullRequest
3 голосов
/ 18 июня 2020

Я попытался сделать следующее в своем компоненте, который использует changeDetection: ChangeDetectionStrategy.OnPush,

@ViewChild('searchInput') input: ElementRef;

ngAfterViewInit() {
    this.searchText$ = fromEvent<any>(this.input.nativeElement, 'keyup')
      .pipe(
        map(event => event.target.value),
        startWith(''),
        debounceTime(300),
        distinctUntilChanged()
      );
}

И в шаблоне

<div *ngIf="searchText$ | async as searchText;">
  results for "<b>{{searchText}}</b>"
</div>

Это не сработает, однако, если я удалю OnPush, да. Я не совсем уверен, почему, поскольку канал asyn c должен запускать обнаружение изменений.

Edit :

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

this.searchText$ = interval(1000);

Без каких-либо @Input канал async отмечает мой компонент для проверки, и он работает нормально. Поэтому я не понимаю, почему у меня нет такого же поведения с fromEvent

Ответы [ 2 ]

1 голос
/ 18 июня 2020

По умолчанию Каждый раз, когда Angular запускает обнаружение изменений, он просматривает все компоненты один за другим и проверяет, не изменилось ли что-то, и обновляет свою DOM, если это так. что произойдет, если вы измените определение изменений по умолчанию на ChangeDetection.OnPush?

Angular изменяет свое поведение, и есть только два способа обновить компонентную DOM.

  • @ Input ссылка на свойство изменена

  • Вызывается вручную markForCheck ()

Если вы выполните одно из этих действий, DOM обновится соответствующим образом. в вашем случае вы не используете первый вариант, поэтому вам придется использовать второй и звонить markForCheck() в любом месте. но есть один случай, когда вы используете конвейер asyn c, он будет вызывать этот метод для вас.

Канал asyn c подписывается на Observable или Promise и возвращает последнее значение, которое оно испустил. Когда выдается новое значение, асинхронный конвейер отмечает компонент, подлежащий проверке на наличие изменений . Когда компонент разрушается, конвейер asyn c автоматически отменяет подписку, чтобы избежать потенциальных утечек памяти.

, поэтому здесь нет ничего magi c, он вызывает markForCheck() под капотом. но если это так, почему ваше решение не работает? Чтобы ответить на этот вопрос, давайте углубимся в сам AsyncPipe. если мы проверим исходный код функции преобразования AsyncPipes, выглядит так:

transform(obj: Observable<any>|Promise<any>|null|undefined): any {
    if (!this._obj) {
      if (obj) {
        this._subscribe(obj);
      }
      this._latestReturnedValue = this._latestValue;
      return this._latestValue;
    }
    ....// some extra code here not interesting
 }

, поэтому, если переданное значение не является неопределенным, она подпишется на этот наблюдаемый объект и будет действовать соответствующим образом (вызовите markForCheck() при появлении значения)

Теперь самая важная часть. первый раз Angular вызывает метод преобразования, он не определен, потому что вы инициализируете searchText$ внутри ngAfterViewInit() обратного вызова (представление уже визуализировано, поэтому он также вызывает asyn c pipe). Итак, когда вы инициализируете поле searchText$, обнаружение изменений уже завершено для этого компонента, поэтому он не знает, что searchText$ был определен, и впоследствии он больше не вызывает AsyncPipe, поэтому проблема в том, что он никогда не получает к AsyncPipe, чтобы подписаться на эти изменения, вам нужно вызвать markForCheck() только один раз после инициализации, Angular снова запустить changeDetection для этого компонента, обновить DOM и вызвать AsyncPipe, который будет подписываться на этот наблюдаемый

ngAfterViewInit() {
    this.searchText$ =
     fromEvent<any>(this.input.nativeElement, "keyup").pipe(
      map((event) => event.target.value),
      startWith(""),
      debounceTime(300),
      distinctUntilChanged()
    );
    this.cf.markForCheck();
  }
1 голос
/ 18 июня 2020

changeDetection: ChangeDetectionStrategy.OnPush позволяет компоненту не запускать changeDetection все время, а только при обновлении ссылки @Input (). Поэтому, если вы выполняете все свои действия в одном компоненте, ссылки @Input () не обновляются, поэтому представление не обновляется.

Я предлагаю вам создать свой тупой компонент с помощью кода шаблона выше, но передать ему searchText через @Input () и вызвать тупой компонент в смарт-компоненте

Смарт-компонент

<my-dumb-component [searchText]="searchText$ | async"></my-dumb-component>

Простой компонент

@Input() searchText: SearchText

шаблон

<div *ngIf="searchText">
  results for "<b>{{searchText}}</b>"
</div>
...