在父组件中使用单个 ngOnDestroy

Using single ngOnDestroy in parent component

在我们的应用程序中,我们有一些组件继承自 BaseComponent

基础组件实现 OnDestroy 接口并使用 Subject 发出以通知子级取消订阅最终打开的流。

这是一种可接受的方法还是每个组件都应该有自己的 ngOnDestroy 实现?从此 article 我了解到生命周期挂钩 不是 继承的。

基础组件

export abstract class ComponentBase implements OnDestroy {
  protected destroy$ = new Subject<boolean>();

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

子组件

export class ChildComponent extends ComponentBase implements OnInit {

 ngOnInit() {
    this.service
      .getById(userId)
      .pipe(takeUntil(this.destroy$)) // this is emitted by the parent
      .subscribe(user => { ...}
 }
}

这是一种有效的方法,但最好使用异步管道而不是订阅并添加 onPushStrategy 以获得更好的性能,如果你想实现 https://blog.angular-university.io/onpush-change-detection-how-it-works/

2020 年 9 月 9 日更新:
我们已经在工作中发布了一个库,它可以安全地与 Ivy 一起使用,并且可以让您访问所有组件生命周期作为可观察对象:https://github.com/cloudnc/ngx-observable-lifecycle

也就是说,本应在 Angular 本身中修复此上游的 PR 最近已合并并发布(不记得发布但看一看!)

2020 年 5 月 18 日更新:
如此处所述,以下内容不再适用于 Ivy:https://github.com/angular/angular/issues/36776

这里有一个恢复该行为的 PR:https://github.com/angular/angular/pull/35464

尽管它不那么灵活,但同时您可以使用它:https://github.com/ngneat/until-destroy


如果您使用 ComponentBase 只允许您访问 destroy$ 我会说这有点矫枉过正。您可能需要扩展另一个 class 并且 Typescript 不支持多重继承,因此请尽量避免这种情况,除非确实有必要。

也就是说,如果您只是想寻找一种以反应方式轻松取消订阅的方法,请查看以下内容:

/**
 * Easily unsubscribe from an observable stream by appending `takeUntilDestroyed(this)` to the observable pipe.
 * If the component already has a `ngOnDestroy` method defined, it will call this first.
 * Note that the component *must* implement OnDestroy for this to work (the typings will enforce this anyway)
 */
export function takeUntilDestroyed<T>(component: OnDestroy): (source: Observable<T>) => Observable<T> {
  return (source: Observable<T>): Observable<T> => {
    const onDestroy = new Subject();
    const previousOnDestroy = component.ngOnDestroy;

     component.ngOnDestroy = () => {
      if (previousOnDestroy) {
        previousOnDestroy.apply(component);
      }

       onDestroy.next();
      onDestroy.complete();
    };

     return source.pipe(takeUntil(onDestroy));
  };
}

然后从您的组件中执行 this.myObs$.pipe(takeUntilDestroyed(this))

您也可以在此 PR 中找到该功能和演示用法:
https://github.com/cloudnc/ngx-sub-form/pull/41/files#diff-5796510f30fdd5bede9d709ce53ef225R45

Is this an acceptable approach or rather every single component should have its own ngOnDestroy implementation?

是否可以接受只是见仁见智,但我强烈不鼓励这种方法,原因如下。

@Component({...})
export class MyComponent extends BaseComponent implements OnDestroy {
      public ngOnDestroy() {
         // do work
      }
}

export class BaseComponent implements OnDestroy {
      public ngOnDestroy() {
         // never executed
      }
}

上面的示例没有发出 TypeScript 警告,即从未执行 super 方法,因此我很想将其称为 anti-pattern.

此外,将来您会 添加额外的生命周期挂钩,例如 OnInit 到基础 class。为此,您必须搜索所有 后代 以确保他们调用 super.ngOnInit().

虽然看起来工作量更大,但使用封装更安全

export class BaseComponent implements OnDestroy {
     public ngOnDestroy() {
        // do work
     }
}

@Component({...})
export class MyComponent implements OnDestroy {
  private readonly _base: BaseComponent = new BaseComponent();
  public ngOnDestroy() {
     this._base.ngOnDestroy();
  }
}

网上有很多关于pros/cons封装与继承的文章。这在计算机科学中其实是一个冗长的讨论,有些编程语言不支持继承就是因为这些问题。我想我想说的是,这是一个广泛的话题,是您个人选择的问题,但这也是您问这个问题的部分原因。

https://www.developer.com/design/article.php/3525076/Encapsulation-vs-Inheritance.htm