You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular项目退订过程中出现错误及每秒调用请求触发TypeError的修复方案咨询

解决退订时出现的Cannot read property 'remove' of null错误

我来帮你分析并解决这个问题,这个错误的核心原因是重复创建loading遮罩、多次调用隐藏方法时找不到DOM元素,加上组件销毁时的订阅时机问题导致的。咱们一步步来修复:

问题出在哪?

  1. 你的load()方法每秒执行一次,每次都调用loadingBackdrop.show()——这会不断给页面body添加同一个id的遮罩元素,但DOM里id是唯一的,后续的show()其实没意义,而且第一次hide()就把这个元素删掉了,后面再调用hide()自然找不到元素,就抛出了null的错误。
  2. 当组件销毁(ngOnDestroy)时,你退订了timer的订阅,但此时可能还有未完成的请求,finalize()还是会执行hide(),这时候遮罩已经被移除,就会触发报错。

具体修复步骤

1. 给hide()方法加空值检查(最直接的修复)

先在loading-backdrop.service.ts的hide()方法里,先确认元素存在再执行移除操作:

hide() { 
  const backdropWrapperElement = document.querySelector('#loadingBackdrop'); 
  // 先检查元素是否存在,避免调用null的remove方法
  if (backdropWrapperElement) {
    backdropWrapperElement.remove(); 
  }
} 

2. 避免重复创建遮罩元素

每次调用show()之前,先检查页面上是否已经有这个遮罩了,不要重复添加:

show() { 
  // 先判断遮罩是否已存在,存在就直接返回,不重复创建
  const existingBackdrop = document.querySelector('#loadingBackdrop');
  if (existingBackdrop) {
    return;
  }
  const backdropWrapperElement = this.createLoadingBackdropTemplate(); 
  const bodyElement = document.querySelector('body'); 
  bodyElement.appendChild(backdropWrapperElement); 
} 

3. 优化订阅管理,让组件销毁时更干净

你现在手动管理ngUnsubscribe的方式可以换成更简洁的takeUntil模式,同时确保组件销毁时清理掉遮罩:
修改payment.component.ts:

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

// ...

// 定义一个销毁信号,用来终止所有订阅
private destroy$ = new Subject<void>();

ngOnInit() { 
  this.load(); 
} 

load() { 
  this.loadingBackdrop.show(); 
  timer(0, 1000).pipe( 
    switchMap(() => this._object.get(this.card.card_id) 
      .pipe(
        finalize(() => this.loadingBackdrop.hide()),
        // 每个请求都监听销毁信号,组件销毁时立即终止请求
        takeUntil(this.destroy$)
      )),
    // 整个timer流也监听销毁信号
    takeUntil(this.destroy$)
  ).subscribe(data => this.dataSource = new MatTableDataSource(data)); 
} 

ngOnDestroy() { 
  // 发送销毁信号,终止所有订阅
  this.destroy$.next();
  this.destroy$.complete();
  // 组件销毁时手动调用一次hide,确保遮罩被清理
  this.loadingBackdrop.hide();
}

这样做的好处是:

  • 组件销毁时,所有正在进行的请求和timer都会立即终止,减少不必要的finalize调用
  • 手动在销毁时清理遮罩,避免页面上残留遮罩元素

额外小优化(可选)

因为你每秒发起一次请求,switchMap会取消前一个未完成的请求,这时候前一个请求的finalize会触发hide(),但新请求又会调用show(),可能导致遮罩闪烁。如果想避免这个问题,可以把finalize移到整个timer流的外面,确保只有当整个流终止时才隐藏遮罩:

load() { 
  this.loadingBackdrop.show(); 
  timer(0, 1000).pipe( 
    switchMap(() => this._object.get(this.card.card_id)),
    takeUntil(this.destroy$),
    finalize(() => this.loadingBackdrop.hide())
  ).subscribe(data => this.dataSource = new MatTableDataSource(data)); 
} 

这样遮罩只会在组件销毁或者timer流终止时才隐藏,不会因为请求被取消而频繁显示隐藏。

内容的提问来源于stack exchange,提问作者user14873824

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 03:49:08