如何在ngOnInit中初始化Angular响应式表单并解决变量未初始化报错?
问题描述
尝试通过订阅Observable初始化Angular响应式表单,在ProductFormComponent的ngOnInit钩子中订阅productService的BehaviorSubject获取product对象,但创建FormControl时出现**"Property 'product' is used before its initialisation"**错误。原因是表单作为类成员属性,在ngOnInit执行前就已完成初始化,此时product还未被赋值。
相关组件代码
product-form-component.ts
export class ProductFormComponent { productReceived: Subscription; editMode: boolean = false; product: product; constructor(private productService: ProductService) { } ngOnInit() { this.productReceived = this.productService.$emitProduct.subscribe((product: product) => { if (product) { this.product = product; this.editMode = true; } }); } productForm = new FormGroup({ name: new FormControl(`${this.product}`, [Validators.required, Validators.minLength(3)]), // 报错行 description: new FormControl(''), specification: new FormControl(''), price: new FormControl(''), imagePath: new FormControl(''), warranty: new FormControl(''), }); ngOnDestroy() { this.productReceived.unsubscribe(); } }
product-item.component.html
<div style="cursor: pointer; width: 450px;" class="card rounded p-3" aria-current="true"> <div> <div class="d-flex w-100 justify-content-between mb-1"> <div class="d-flex" style="gap: 0.75rem;"> <h5>{{ product.name | titlecase}}</h5> <span style="color: darkred;">${{product.price}}</span> </div> <div class="dropdown"> <button class="btn btn-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> Manage Product </button> <ul class="dropdown-menu"> <li><a class="dropdown-item" >Add to Cart</a></li> <li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#staticBackdrop" style="cursor: pointer;" (click)="formOpen(product)">Edit</a></li> <li><a class="dropdown-item" style="cursor: pointer;">Delete</a></li> </ul> </div> </div> <div class="d-flex flex-row align-items-center" [routerLink]="product.name" (click)="emitProduct(product)"> <img [src]="product.imagePath[0]" style="max-height: 175px"> <ul class="product-desc"> <li>{{productDescList[0]}}</li> <li>{{productDescList[1]}}</li> </ul> </div> </div> </div> <app-modal-pop></app-modal-pop>
product-form.component.html
<form [formGroup]="productForm"> <h5>{{editMode? 'Edit Product': 'Create Product'}}</h5> <div> <app-input-field label="Name" [control]="productForm.get('name')"></app-input-field> </div> <div> <app-input-field label="Description" [control]="productForm.get('description')"></app-input-field> </div> <div> <app-input-field label="Specification" [control]="productForm.get('specification')"></app-input-field> </div> <div> <app-input-field label="Price" [control]="productForm.get('price')"></app-input-field> </div> <div> <app-input-field label="Image Path" [control]="productForm.get('imagePath')"></app-input-field> </div> <div> <app-input-field label="Warranty" [control]="productForm.get('warranty')"></app-input-field> </div> <button type="submit" class="btn btn-secondary">Create</button> </form>
input-field.component.html
<label class="label">{{label}}:</label> <input type="text" [formControl]="control" id="name" [ngClass]="{'alert-danger': showErrors()}"> <ng-container *ngIf="control.errors && control.dirty && control.touched"> <div style="color: red;" *ngIf="control.errors.required"> Name is required! </div> <div style="color: red;" *ngIf="control.errors.minlength"> 3 minimum characters! </div> </ng-container>
解决方法
方案1:初始化空表单,订阅后用patchValue赋值
先创建带有验证规则的空表单,在订阅到product数据后,用patchValue填充表单字段,同时切换编辑模式:
export class ProductFormComponent { productReceived: Subscription; editMode: boolean = false; product: product; // 初始化空表单并设置验证规则 productForm = new FormGroup({ name: new FormControl('', [Validators.required, Validators.minLength(3)]), description: new FormControl(''), specification: new FormControl(''), price: new FormControl(''), imagePath: new FormControl(''), warranty: new FormControl(''), }); constructor(private productService: ProductService) { } ngOnInit() { this.productReceived = this.productService.$emitProduct.subscribe((product: product) => { if (product) { this.product = product; this.editMode = true; // 用product数据填充表单字段 this.productForm.patchValue({ name: product.name, description: product.description, specification: product.specification, price: product.price, imagePath: product.imagePath, warranty: product.warranty }); } else { // 无product时重置表单到创建模式 this.editMode = false; this.productForm.reset(); } }); } ngOnDestroy() { this.productReceived.unsubscribe(); } }
方案2:延迟初始化表单(在ngOnInit中创建)
将productForm声明为可选类型,在ngOnInit中根据是否获取到product来创建表单:
export class ProductFormComponent { productReceived: Subscription; editMode: boolean = false; product: product; // 声明为可选类型,避免初始化报错 productForm?: FormGroup; constructor(private productService: ProductService) { } ngOnInit() { this.productReceived = this.productService.$emitProduct.subscribe((product: product) => { this.editMode = !!product; // 根据product是否存在创建表单并赋值 this.productForm = new FormGroup({ name: new FormControl(product?.name || '', [Validators.required, Validators.minLength(3)]), description: new FormControl(product?.description || ''), specification: new FormControl(product?.specification || ''), price: new FormControl(product?.price || ''), imagePath: new FormControl(product?.imagePath || ''), warranty: new FormControl(product?.warranty || ''), }); this.product = product; }); } ngOnDestroy() { this.productReceived.unsubscribe(); } }
注意:模板中需要添加安全导航操作符*ngIf="productForm",避免表单未初始化时报错:
<form *ngIf="productForm" [formGroup]="productForm">
方案3:使用FormBuilder简化表单创建
结合FormBuilder简化表单代码,逻辑与方案1一致,但代码更简洁:
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; export class ProductFormComponent { productReceived: Subscription; editMode: boolean = false; product: product; productForm: FormGroup; constructor( private productService: ProductService, private fb: FormBuilder // 注入FormBuilder ) { // 用FormBuilder初始化空表单 this.productForm = this.fb.group({ name: ['', [Validators.required, Validators.minLength(3)]], description: [''], specification: [''], price: [''], imagePath: [''], warranty: [''] }); } ngOnInit() { this.productReceived = this.productService.$emitProduct.subscribe((product: product) => { if (product) { this.product = product; this.editMode = true; // 直接传入product对象,字段名匹配即可自动填充 this.productForm.patchValue(product); } else { this.editMode = false; this.productForm.reset(); } }); } ngOnDestroy() { this.productReceived.unsubscribe(); } }
内容的提问来源于stack exchange,提问作者shahiedul
相关产品推荐
相关产品推荐

