Angular项目退订过程中出现错误及每秒调用请求触发TypeError的修复方案咨询
解决退订时出现的
Cannot read property 'remove' of null错误 我来帮你分析并解决这个问题,这个错误的核心原因是重复创建loading遮罩、多次调用隐藏方法时找不到DOM元素,加上组件销毁时的订阅时机问题导致的。咱们一步步来修复:
问题出在哪?
- 你的
load()方法每秒执行一次,每次都调用loadingBackdrop.show()——这会不断给页面body添加同一个id的遮罩元素,但DOM里id是唯一的,后续的show()其实没意义,而且第一次hide()就把这个元素删掉了,后面再调用hide()自然找不到元素,就抛出了null的错误。 - 当组件销毁(
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
相关产品推荐
相关产品推荐

