首页 > 解决方案 > 订阅 *ngIf 背后的 DOM 事件

问题描述

我希望能够将 DOM 事件作为可观察流处理,调用过滤器、concatMap 等,如下例所示。

@Component({
  template: `<button #btn>Submit<button`,
  selector: 'app-test',
})
class TestComponent implements AfterViewInit, OnDestroy {

  private destroy$ = new Subject<boolean>();

  @ViewChild('btn') private btn: ElementRef<HtmlButtonElement>;

  constructor(private testService: TestService) { }

  ngAfterViewInit() {
    fromEvent(btn.nativeElement, 'click').pipe(
      exhaustMap(() = > {
        return this.testService.save();
      }),
      takeUntil(this.destroy$),
    ).subscribe();
  }

  ngOnDestroy() {
    this.destroy$.next(true);
    this.destroy$.complete();
  }
}

但有时我想监听事件的元素位于 *ngIf 后面,并且在 ngAfterViewInit 运行时不存在。仍然以反应方式侦听事件的最佳方法是什么?

ngOnViewChecked我尝试的一种方法是在检查是否存在的 if 语句后面设置与上述相同的订阅ElementRef,并使用标志来避免多个订阅。但我发现那很乱。

做这样的事情是一个好习惯吗?

@Component({
  template: `<button (click)="clickEvent.emit()">Submit<button`,
  selector: 'app-test',
})
class TestComponent implements OnInit, OnDestroy {

  private destroy$ = new Subject<boolean>();

  clickEvent = new EventEmitter<void>();

  constructor(private testService: TestService) { }

  ngOnInit() {
    this.clickEvent.pipe(
      exhaustMap(() = > {
        return this.testService.save();
      }),
      takeUntil(this.destroy$),
    ).subscribe();
  }

  ngOnDestroy() {
    this.destroy$.next(true);
    this.destroy$.complete();
  }
}

我应该用 替换EventEmitterSubject?有没有比这些更好的方法?

编辑:为了清楚起见,我的问题与订阅由于 *ngIf 而可能不存在的元素的事件有关

标签: angularrxjs

解决方案


@ViewChildren与 a一起使用QueryList来监听 DOM 变化。

import { Component, AfterViewInit, OnDestroy, ElementRef, ViewChildren, QueryList } from '@angular/core';
import { Subject, fromEvent, of } from 'rxjs';
import { exhaustMap, takeUntil, switchMap, map, filter } from 'rxjs/operators';

@Component({
  selector: 'app-test',
  template: `<button #btn>Submit<button`
})
export class TestComponent implements AfterViewInit, OnDestroy {

  private destroy$ = new Subject<boolean>();

  // Any time a child element is added, removed, or moved, 
  // the query list will be updated, and the changes observable 
  // of the query list will emit a new value.
  @ViewChildren('btn') private btn: QueryList<ElementRef>;

  constructor(private testService: TestService) { }

  ngAfterViewInit() {
    this.btn.changes.pipe(
      // only emit if there is an element and map to the desired stream
      filter((list: QueryList<ElementRef>) => list.length > 0),
      switchMap((list: QueryList<ElementRef>) => fromEvent(list.first.nativeElement, 'click')),
      // add your other operators
      exhaustMap(() => this.testService.save()),
      takeUntil(this.destroy$),
    ).subscribe(console.log);
    // Trigger a change event to emit the current state
    this.btn.notifyOnChanges()
  }

  ngOnDestroy() {
    this.destroy$.next(true);
    this.destroy$.complete();
  }
}

https://stackblitz.com/edit/angular-ivy-iklbs9


推荐阅读