如何修复此特定 NG0100: ExpressionChangedAfterItHasBeenCheckedError 错误?

mar*_*zzz 9 angular

我读过有关 NG0100: ExpressionChangedAfterItHasBeenCheckedError in Angular 的内容,但在我的情况下我无法避免它。

基本上,在拦截器上,我有一个加载“状态”真/假的服务:

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
    this.showLoader();

    return next.handle(req).pipe(
      catchError((error) => {
        return error;
      }),
      finalize(() => {
        this.hideLoader();
      })
    );
  }
Run Code Online (Sandbox Code Playgroud)

在应用程序组件内使用ngAfterViewInit会引入该错误:

  ngOnInit(): void {}
  ngAfterViewInit() {
    this.getData().subscribe((data) => {
      this.childSelector.loadRecipeRoadmap(data.name);
    });
  }
Run Code Online (Sandbox Code Playgroud)

我需要使用它:事实上,当加载所有子项时,父项必须“发送”(一次)数据给子项(仅在开始时)。在某些时候,我只需要从孩子那里读取(这就是我使用 ViewChild 而不是 @Output 机制的原因)。

我该如何修复这个特定错误?我应该同步 Observable 吗?不知道如何...

car*_*cki 38

这是因为您正在执行 中的一些代码ngAfterViewInit,这会修改显示的数据

第一个检测周期评估ngIffalse,然后ngAfterViewInit执行,然后执行第二个验证检测周期(角度展开模式有这个附加的),这次ngIf评估为true。因此出现了臭名昭著的错误。

对此有几种解决方案:

选项 1:下一个宏任务(setTimeout)

由于您的数据获取无论如何都是异步的,因此您可以在 0 时间延迟ngAfterViewInit的帮助下推迟它在下一个宏任务(完成后)中调用:setTimeout

ngAfterViewInit() {
  setTimeout(() => {
    this.getData().subscribe((data) => {
      this.childSelector.loadRecipeRoadmap(data.name);
    });
  });
}
Run Code Online (Sandbox Code Playgroud)
选项 2:ChangeDetectorRef.detectChanges()

您可以在修改显示的数据后通知 Angular 运行额外的检测周期。

constructor(private http: HttpClient, public loader: LoaderService, private changeDetectorRef: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.getData().subscribe((data) => {
      this.childSelector.loadRecipeRoadmap(data.name);
    });
    this.changeDetectorRef.detectChanges();
  }
Run Code Online (Sandbox Code Playgroud)

或者也许更有角度的方式:

选项3:在OnInit + 数据绑定中获取数据

不要等待子组件创建并直接与其交互,而是执行数据加载OnInit并将数据绑定到子组件:

export class AppComponent {
  dataForChild: any;

  constructor(private http: HttpClient, public loader: LoaderService) {}

  ngOnInit(): void {
    this.getData().subscribe((data) => this.dataForChild = data.name);
  }
Run Code Online (Sandbox Code Playgroud)

并将其绑定(当然myData在子组件中需要成为并输入属性@Input()

<app-my-child [myData]="dataForChild"></app-my-child>
Run Code Online (Sandbox Code Playgroud)

额外待订阅

您正在订阅 observable,但不要取消订阅(既不使用unsubribe()也不使用takeUntil())。虽然从 Angular http 模块订阅 observables 不会导致内存泄漏,但最好确保您无论如何都会取消订阅,原因如下:

  • 只是因为这是一个很好的做法,而不考虑是否有必要。今天可能没有必要,但是明天有人会修改服务,它可能不再是安全可观察的;
  • 如果您取消订阅ngOnDestroy,待处理的 http 请求将被取消,从而释放已用请求的浏览器池
  • 订阅中的逻辑和潜在pipe的转换中也不会被调用,因此由于不执行不必要的代码而具有更好的性能
  • 它将允许垃圾收集器更快地删除组件实例