Angular通过内容投影添加条件表单字段 解决找不到控件错误
问题描述
我正在尝试通过内容投影添加条件表单字段。
目前投影的输入字段可正常根据同级复选框状态切换显隐:复选框处于“勾选”状态时条件字段显示,处于“未勾选”状态时输入字段消失。
但当我将复选框切换为勾选状态时,会收到如下错误:
ERROR Error: Cannot find control with name: 'detail'
我希望投影的detail字段归属于子组件内部的_formGroup,而非父组件的FormGroup,该如何实现?
该错误表明formControlName="detail"无法被子组件的_formGroup识别,该如何修复?
以下为相关代码:
父组件
import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; @Component({ selector: 'ch-parent-comp', templateUrl: './parent-comp.component.html', styleUrls: ['./parent-comp.component.scss'] }) export class ParentComponent implements OnInit { formGroup: FormGroup; constructor() { this.formGroup = new FormGroup({ //detail: new FormControl('') --> 加这行能解决问题,但我不想把这个字段放在父组件,我要它归属于子组件 }); } ngOnInit(): void { } }
父组件HTML模板
<form> <ch-check-box-group [parentFormGroup]="formGroup" controlName="new_property" controlLabel="New Property" [hasDetail]="true" [detailRequired]="true"> <!-- 投影的输入字段 --> <ng-template contentHandle> <mat-form-field> <mat-label>Purchase Date</mat-label> <input type="text" matInput name="detail" formControlName="detail" id=""> </mat-form-field> </ng-template> </ch-check-box-group> </form>
子组件(ch-check-box-group)
export interface CheckboxGroupForm { value: FormControl<boolean>, detail?: FormControl<any> } @Component({ selector: 'ch-check-box-group', templateUrl: './check-box-group.component.html', styleUrls: ['./check-box-group.component.scss'] }) export class CheckBoxGroupComponent implements OnInit, OnDestroy { private _ks: Subject<void> = new Subject<void>(); _formGroup: FormGroup _showDetailInput: boolean = false; @Input() controlLabel!: string; @Input() controlName!: string; @Input() parentFormGroup!: FormGroup; @Input() hasDetail: boolean = false; @Input() detailRequired: boolean = false; @ContentChild(ContentHandleDirective) content!: ContentHandleDirective; constructor() { this._formGroup = new FormGroup<CheckboxGroupForm>({ value : new FormControl(false, {nonNullable: true}), }); } ngOnInit(): void { if(this.hasDetail){ this._formGroup.addControl('detail', new FormControl('')); } this.parentFormGroup.addControl(this.controlName, this._formGroup); this._formGroup.setParent(this.parentFormGroup); this._formGroup.valueChanges .pipe(takeUntil(this._ks)) .subscribe((change) => { // 切换详情输入框显隐 if(this.hasDetail && this._showDetailInput != change.value){ this._showDetailInput = change.value; } // 按需切换详情输入框校验规则 if(this.hasDetail && this.detailRequired && change.value){ setTimeout(() => { this._formGroup.get('detail')?.addValidators(Validators.required); }); } else { this._formGroup.get('detail')?.clearValidators(); if(this._showDetailInput != change.value){ this._formGroup.get('detail')?.updateValueAndValidity(); } } }); } ngOnDestroy(){ this._ks.next(); this._ks.complete(); } }
子组件模板
<div class="row"> <form class="col-12" [formGroup]="_formGroup"> <mat-checkbox formControlName="value" i18n>{{ controlLabel }}</mat-checkbox> <button mat-icon-button color="primary"> <mat-icon>help</mat-icon> </button> <div class="conditional-input-box-container" *ngIf="_showDetailInput"> <ng-container [ngTemplateOutlet]="content.templateRef"></ng-container> </div> </form> </div>
问题原因
报错核心是Angular模板注入上下文的继承规则:写在父组件中的ng-template,即使最终通过ngTemplateOutlet在子组件内渲染,它的表单控制注入上下文仍归属父组件,不会自动获取子组件模板中[formGroup]="_formGroup"提供的FormGroupDirective实例。因此formControlName="detail"会默认去父组件根formGroup中查找detail控件,自然匹配失败。
修复方案
渲染模板时把子组件的FormGroupDirective作为模板上下文传入,让投影内容能正确获取子组件的表单组实例,共三步修改:
- 第一步:在子组件类中注入自身的
FormGroupDirective实例
// 引入对应依赖 import { FormGroupDirective } from '@angular/forms'; export class CheckBoxGroupComponent implements OnInit, OnDestroy { // 原有属性声明不变 constructor( // 注入当前子组件表单对应的FormGroupDirective private _formGroupDirective: FormGroupDirective ) { this._formGroup = new FormGroup<CheckboxGroupForm>({ value : new FormControl(false, {nonNullable: true}), }); } // 其余原有逻辑不变 }
- 第二步:修改子组件模板的
ngTemplateOutlet配置,将注入的表单指令作为上下文传递
<div class="row"> <form class="col-12" [formGroup]="_formGroup"> <mat-checkbox formControlName="value" i18n>{{ controlLabel }}</mat-checkbox> <button mat-icon-button color="primary"> <mat-icon>help</mat-icon> </button> <div class="conditional-input-box-container" *ngIf="_showDetailInput"> <ng-container [ngTemplateOutlet]="content.templateRef" [ngTemplateOutletContext]="{ formGroupDirective: _formGroupDirective }"> </ng-container> </div> </form> </div>
- 第三步:修改父组件中的投影模板,显式绑定传入的子组件表单组
<ng-template contentHandle let-formGroupDirective="formGroupDirective"> <ng-container [formGroup]="formGroupDirective.form"> <mat-form-field> <mat-label>Purchase Date</mat-label> <input type="text" matInput name="detail" formControlName="detail" id=""> </mat-form-field> </ng-container> </ng-template>
修改完成后,投影的detail控件会正确注册到子组件内部的_formGroup上,既不会再报找不到控件的错误,也不需要在父组件FormGroup中声明detail字段。
内容的提问来源于stack exchange,提问作者calbear47
相关产品推荐
相关产品推荐

