Angular中使用@ViewChild访问继承类方法报错问题排查
Angular组件继承类方法调用报错问题
问题描述
在Angular应用中,通过@ViewChild访问组件继承类中的isRegistrationCompleted方法时触发报错,但将该方法直接放在ProductRegistrationComponent中时可正常运行。
报错信息
screen-product-registration.component.ts:31 ERROR TypeError: Cannot read properties of undefined (reading 'isRegistrationCompleted')
简化代码结构
screen-product-registration.component.html
<div class="row full-height"> <app-product-registration #productRegistration></app-product-registration> </div>
ScreenProductRegistrationComponent
@Component({ selector: 'app-product-registration-screen', templateUrl: './product-registration-screen.component.html', styleUrls: ['./product-registration-screen.component.css'] }) export class ProductRegistrationScreenComponent implements OnInit{ @ViewChild(ProductRegistrationComponent) productRegistration!: ProductRegistrationComponent; ngOnInit() { this.startContinuousUpdate(); } startContinuousUpdate() { setInterval(() => { const listNeedsLoading = this.productRegistration.isRegistrationCompleted(); if (listNeedsLoading) { console.log("Entered inside IF"); } }, 1000); } }
product-registration.component.ts
@Component({ selector: 'app-product-registration', templateUrl: './product-registration.component.html', styleUrls: ['./product-registration.component.css'] }) @Injectable({ providedIn: 'root', }) export class ProductRegistrationComponent extends ProductService{ }
product.service.ts
export class ProductService { private registrationCompleted: boolean = false; isRegistrationCompleted(){ return this.registrationCompleted; } loadProducts() { console.log("it works"); this.registrationCompleted = !this.registrationCompleted; } }
问题原因分析
- 组件初始化时机不匹配:
ngOnInit执行时视图尚未完全初始化,@ViewChild获取的productRegistration实例可能还未创建完成,此时调用其方法会触发undefined错误。 - 组件与服务装饰器冲突:
ProductRegistrationComponent同时使用@Component和@Injectable装饰器,导致Angular实例化逻辑冲突,影响组件实例的正常生成和继承关系。 - 不符合Angular设计规范:组件继承服务并非Angular推荐的实践,服务应通过依赖注入使用,继承方式容易引发实例化和生命周期管理问题。
解决方案
方案一:调整视图初始化时机
将startContinuousUpdate的调用移至ngAfterViewInit钩子(此时视图已初始化,@ViewChild能正确获取实例),同时增加空值检查避免意外报错:
@Component({ selector: 'app-product-registration-screen', templateUrl: './product-registration.component.html', styleUrls: ['./product-registration.component.css'] }) export class ProductRegistrationScreenComponent implements OnInit, AfterViewInit{ @ViewChild(ProductRegistrationComponent) productRegistration!: ProductRegistrationComponent; ngOnInit() {} ngAfterViewInit() { this.startContinuousUpdate(); } startContinuousUpdate() { setInterval(() => { if (this.productRegistration) { const listNeedsLoading = this.productRegistration.isRegistrationCompleted(); if (listNeedsLoading) { console.log("Entered inside IF"); } } }, 1000); } }
方案二:移除组件上的@Injectable装饰器
组件无需@Injectable装饰器,移除后确保组件实例正常创建:
@Component({ selector: 'app-product-registration', templateUrl: './product-registration.component.html', styleUrls: ['./product-registration.component.css'] }) export class ProductRegistrationComponent extends ProductService{ }
方案三:重构为依赖注入模式(推荐)
遵循Angular最佳实践,组件通过依赖注入使用服务,而非继承:
- 修改
ProductRegistrationComponent:
@Component({ selector: 'app-product-registration', templateUrl: './product-registration.component.html', styleUrls: ['./product-registration.component.css'] }) export class ProductRegistrationComponent { constructor(public productService: ProductService) {} // 封装服务方法对外暴露 isRegistrationCompleted() { return this.productService.isRegistrationCompleted(); } }
- 为
ProductService添加@Injectable装饰器:
@Injectable({ providedIn: 'root', }) export class ProductService { private registrationCompleted: boolean = false; isRegistrationCompleted(){ return this.registrationCompleted; } loadProducts() { console.log("it works"); this.registrationCompleted = !this.registrationCompleted; } }
内容的提问来源于stack exchange,提问作者JamesB
相关产品推荐
相关产品推荐

