Angular组件从服务调用函数时HTMLElement未定义问题排查
Angular自定义Snackbar组件@ViewChild获取元素undefined问题分析
问题描述
我参考教程实现了错误服务,报错时显示自定义Snackbar(替代Angular Material的Snackbar)。尝试用ngIf、ngClass或直接操作DOM控制显隐时发现:从其他服务调用该组件的open/close方法时,@ViewChild获取的snackbar元素为undefined,但组件内部调用这些方法时完全正常。该组件始终存在于主组件中,仅内部div需要切换显示状态。
相关代码:
import { Component, ElementRef, Injectable, ViewChild } from "@angular/core"; @Component({ selector: "app-snackbar", templateUrl: "./snackbar.component.html", styleUrls: ["./snackbar.component.scss"] }) @Injectable({ providedIn: "root" }) export class SnackbarComponent { @ViewChild("snackbar", { static: false }) snackbar: ElementRef < HTMLDivElement > = {} as ElementRef; message = ""; ngAfterViewInit(): void { console.log("afterViewInit"); console.log(this.snackbar); } open(message: string): void { console.log("open"); this.message = message; console.log(this.snackbar) // => 此处为undefined //this.snackbar.nativeElement.classList.remove("hidden"); } close(): void { this.message = ""; console.log(this.snackbar) this.snackbar.nativeElement.classList.add("hidden"); } }
核心原因
你把SnackbarComponent同时标记为@Component和@Injectable({ providedIn: "root" }),导致出现了两个完全独立的实例:
- 页面上通过
<app-snackbar>渲染的是组件实例:这个实例会正常走Angular组件生命周期,执行ngAfterViewInit,@ViewChild能正确绑定到DOM元素。 - 其他服务中通过依赖注入拿到的是服务实例:这个实例没有被挂载到DOM,不会执行组件生命周期钩子,
@ViewChild自然找不到对应的元素,所以调用open时snackbar是undefined。
说白了,你调用的是服务实例的方法,但实际显示在页面上的是另一个组件实例,两者根本不是同一个对象。
解决方向
- 移除
SnackbarComponent上的@Injectable装饰器,组件就是组件,别把它当服务用。 - 单独创建一个
SnackbarService服务,用RxJS的Subject来传递显示/隐藏的消息。页面上的SnackbarComponent订阅这个服务的消息,收到后再执行自己的open/close逻辑,这样就能保证操作的是页面上的真实组件实例。
内容的提问来源于stack exchange,提问作者Sven Cazier
相关产品推荐
相关产品推荐

