You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular嵌套响应式表单中ngTemplateOutlet使用问题求助

基于JSON自动生成Angular响应式表单的模板上下文问题

我正尝试基于JSON数据生成Angular响应式表单,已通过TypeScript代码生成对应结构的FormGroup,手动编写的HTML表单可正常与FormGroup同步绑定,但使用ng-template和ngTemplateOutlet自动生成HTML表单时,出现"Cannot find control with name"错误,推测是模板未获取到表单上下文,求助如何在模板上下文中传递表单实例以实现表单同步。


用于生成表单的JSON数据

{
    "user":{
        "name": "string",
        "mail":"string",
        "hobbies":{
            "sport":"string",
            "musique":"string",
            "games":"string"
        }
    },
    "house":{
        "adress":"string",
        "size":"string",
        "rooms":{
            "kitchen":"string",
            "bedrooms":{
                "bedroom1":"string",
                "bedroom2":"string"
            }
        }
    }
}

生成FormGroup的TypeScript代码

objectform(data:any): FormGroup{
    let newForm=this.fb.group({})
    for (let item of Object.keys(data)){
      if (data[item]=="string"){
        newForm.addControl(item, this.fb.control('',[]))
      } else {
        newForm.addControl(item,this.objectform(data[item]));
      }
    }
    return newForm;
}

ngOnInit():void {
    this.testForm = this.fb.group({});
    for (let item of Object.keys(this.data)){
      if (this.data[item]!="string"){
        this.testForm.addControl(item,this.objectform(this.data[item]));
      } else{
        this.testForm.addControl(item, this.fb.control('',[]))
      }
    }
    console.log("testForm: ",this.testForm)
}

可正常工作的手动编写HTML代码

<div>
    <form [formGroup]="testForm">
        <div formGroupName="user">
            <h1>User Infos:</h1>
            <mat-form-field appearance="fill" >
                <label>name :</label>
                <textarea matInput formControlName="name"></textarea>
            </mat-form-field>
            <mat-form-field appearance="fill" >
                <label>mail :</label>
                <textarea matInput formControlName="mail"></textarea>
            </mat-form-field>
            <div formGroupName="hobbies">
                <h2>Hobbies:</h2>
                <mat-form-field appearance="fill" >
                    <label>sport :</label>
                    <textarea matInput formControlName="sport"></textarea>
                </mat-form-field>
                <mat-form-field appearance="fill" >
                    <label>musique :</label>
                    <textarea matInput formControlName="musique"></textarea>
                </mat-form-field>
                <mat-form-field appearance="fill" >
                    <label>games :</label>
                    <textarea matInput formControlName="games"></textarea>
                </mat-form-field>
            </div>
        </div>
        <div formGroupName="house">
            <h1>House Infos:</h1>
            <mat-form-field appearance="fill" >
                <label>adress :</label>
                <textarea matInput formControlName="adress"></textarea>
            </mat-form-field>
            <mat-form-field appearance="fill" >
                <label>size :</label>
                <textarea matInput formControlName="size"></textarea>
            </mat-form-field>
            <div formGroupName="rooms">
                <h2>Rooms:</h2>
                <mat-form-field appearance="fill" >
                    <label>kitchen :</label>
                    <textarea matInput formControlName="kitchen"></textarea>
                </mat-form-field>
                <div formGroupName="bedrooms">
                    <h2>bedrooms:</h2>
                    <mat-form-field appearance="fill" >
                        <label>bedroom1 :</label>
                        <textarea matInput formControlName="bedroom1"></textarea>
                    </mat-form-field>
                    <mat-form-field appearance="fill" >
                        <label>bedroom2 :</label>
                        <textarea matInput formControlName="bedroom2"></textarea>
                    </mat-form-field>
                </div>
            </div>
        </div>
    </form>
    <button mat-raised-button (click)="onSubmit()">Submit</button>
</div>

无法正常工作的模板化HTML代码

<div>
    <form [formGroup]="testForm">
        <div [formGroup]="testForm">
            <ng-container *ngFor="let itemname of getKeys(data)">
                <h1>{{itemname}} :</h1>
                <ng-container *ngIf="data[itemname] as item">
                    <ng-container *ngIf="item == 'string'">
                        <ng-container *ngTemplateOutlet="stringparameter; context: {parametername :itemname}"></ng-container>
                    </ng-container>
                    <ng-container *ngIf="item != 'string'" style="padding: 50px">
                        <div formGroupName="{{itemname}}">
                            <ng-container *ngTemplateOutlet="objectparameter; context: {parameter :item}"></ng-container>
                        </div>
                    </ng-container>
                </ng-container>
            </ng-container>
        </div>
        
        <ng-template #stringparameter let-parametername="parametername">
            <mat-form-field appearance="fill" >
                <label>{{parametername}}</label>
                <textarea matInput formControlName="{{parametername}}"></textarea>
            </mat-form-field>
        </ng-template>
        
        <ng-template #objectparameter let-parameter="parameter">
            <ng-container *ngFor="let itemname of getKeys(parameter)">
                <h2>{{itemname}} :</h2>
                <ng-container *ngIf="parameter[itemname] as item">
                    <ng-container *ngIf="item == 'string'">
                        <ng-container *ngTemplateOutlet="stringparameter; context: {parametername :itemname}"></ng-container>
                    </ng-container>
                    <ng-container *ngIf="item != 'string'" style="padding: 50px">
                        <div formGroupName="{{itemname}}">
                            <ng-container *ngTemplateOutlet="objectparameter; context: {parameter :item}"></ng-container>
                        </div>
                    </ng-container>
                </ng-container>
            </ng-container>
        </ng-template>
    </form>
     <button mat-raised-button (click)="onSubmit()">Submit</button>
</div>

错误信息

Error: Cannot find control with name: 'name'
Error: Cannot find control with name: 'mail'
Error: Cannot find control with name: 'hobbies'
Error: Cannot find control with name: 'sport'
Error: Cannot find control with name: 'musique'
etc.

解决方案

问题核心是模板嵌套时未传递当前层级的FormGroup上下文,Angular响应式表单依赖组件树中的表单上下文定位控件,模板渲染无法自动继承父级formGroupName对应的FormGroup,需手动将当前层级的FormGroup传入模板上下文。

步骤1:修改模板上下文,传递当前FormGroup

在ngTemplateOutlet调用时,除了数据结构,额外传递当前对应的FormGroup实例:

根层级模板调用修改

<div [formGroup]="testForm">
    <ng-container *ngFor="let itemname of getKeys(data)">
        <h1>{{itemname}} :</h1>
        <ng-container *ngIf="data[itemname] as item">
            <ng-container *ngIf="item == 'string'">
                <ng-container *ngTemplateOutlet="stringparameter; context: {parametername: itemname, formGroup: testForm}"></ng-container>
            </ng-container>
            <ng-container *ngIf="item != 'string'" style="padding: 50px">
                <div [formGroup]="testForm.get(itemname)">
                    <ng-container *ngTemplateOutlet="objectparameter; context: {parameter: item, formGroup: testForm.get(itemname)}"></ng-container>
                </div>
            </ng-container>
        </ng-container>
    </ng-container>
</div>

objectparameter模板修改

递归调用时传递当前子项对应的FormGroup:

<ng-template #objectparameter let-parameter="parameter" let-formGroup="formGroup">
    <ng-container *ngFor="let itemname of getKeys(parameter)">
        <h2>{{itemname}} :</h2>
        <ng-container *ngIf="parameter[itemname] as item">
            <ng-container *ngIf="item == 'string'">
                <ng-container *ngTemplateOutlet="stringparameter; context: {parametername: itemname, formGroup: formGroup}"></ng-container>
            </ng-container>
            <ng-container *ngIf="item != 'string'" style="padding: 50px">
                <div [formGroup]="formGroup.get(itemname)">
                    <ng-container *ngTemplateOutlet="objectparameter; context: {parameter: item, formGroup: formGroup.get(itemname)}"></ng-container>
                </div>
            </ng-container>
        </ng-container>
    </ng-container>
</ng-template>

stringparameter模板修改

绑定到传入的FormGroup实例,确保控件找到正确上下文:

<ng-template #stringparameter let-parametername="parametername" let-formGroup="formGroup">
    <mat-form-field appearance="fill" >
        <label>{{parametername}}</label>
        <textarea matInput [formControl]="formGroup.get(parametername)"></textarea>
    </mat-form-field>
</ng-template>

步骤2:确保组件有getKeys方法

在组件类中添加遍历对象键的方法:

getKeys(obj: any): string[] {
    return Object.keys(obj);
}

原理说明

  • 原模板仅设置formGroupName,但模板渲染时无法自动关联到对应FormGroup实例,导致控件找不到父级上下文。
  • 手动传递当前层级的FormGroup实例后,控件可通过[formControl]直接绑定到对应实例的控件,避免上下文丢失。
  • 嵌套递归时,每层都传递当前子项的FormGroup,确保所有层级的控件都能找到正确的父级表单组。

内容的提问来源于stack exchange,提问作者agarnier22

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 00:07:01