带有formarray的角度动态表单生成器 [英] Dynamic form Builder in angular with formarray

查看:27
本文介绍了带有formarray的角度动态表单生成器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经在我的项目中实现了动态表单构建器,它运行良好.但是,现在我需要在动态表单构建器中添加一个表单.这就像表单中的表单.所以,我在我的 ts 中创建了一个 formarray 并且它正确地出现了.

我收到类似的错误

错误错误:找不到名称为891713"的控件

这是我实施的https://angular.io/guide/dynamic-form

我使用的代码示例是 https://stackblitz.com/edit/angular-动态表单构建器

所以我在文件夹atoms"中创建了一个名为 formControl 的组件,如果控件是 fromControl,我将再次调用字段控件.

FormControl 组件

 

FormControl 组件 Ts

<预><代码>@Input() 字段:any = {};@Input() 表单:FormGroup;@Output() sendDataToParent = new EventEmitter();//表单:表单组;项目长度:任意;项目:任何;元素数据 = {}行 = [];isDisabled : boolean = false;孩子:FormArray;formNewStatus : boolean = true;//孩子:任何;ngOnInit() {this.items = this.field.items;this.itemLength = this.items.lengththis.items.forEach(元素 => {const elemId =element.id;this.elemData[elemId] = ''});console.log("来自控制表单======>",this.form)this.row.push(this.elemData);}添加表(){const frmArr = this.form.get('childs') as FormArray;this.sendDataToParent.emit(添加");this.row.push(this.elemData)console.log("this.row===>",this.row)}删除行(x){this.row.splice(x, 1);}删除所有行(){console.log(来到这里")this.row = [];}

在父组件中,我已经添加了formarray的代码来添加更多

 onFormControlValueChange(parentField:any){if(parentField === '添加'){this.childs = this.form.get('childs') as FormArray;this.childs.push(this.fb.group(this.createItem()));}console.log("this form====>", this.form)}

谁能帮我解决这个问题?提前致谢!:)

解决方案

噗,这是一个庞大而复杂的改进代码以允许 fromGroups 和 formArray,但我们将尝试.

首先我们要更改一些 DynamicFormBuilderComponent

我们将创建一个函数 getForm 来递归创建 formGroup

 getForm(group: FormGroup, fields: any[]) {for(让 f 个字段){开关(f.type){案例组":group.addControl(f.name, new FormGroup({}));this.getForm(group.get(f.name) as FormGroup, f.children);休息;案例复选框":group.addControl(f.name, new FormGroup({}));const groupOption = group.get(f.name) as FormGroup;for(让选择 f.options){groupOption.addControl(opt.key, new FormControl(opt.value));}休息;案例数组":group.addControl(f.name, new FormArray([]));const array = group.get(f.name) as FormArray;如果(f.value){f.value.forEach(x=>array.push(this.addGroupArray(f.children)))array.patchValue(f.value)}休息;默认:group.addControl(f.名称,new FormControl(f.value || "", Validators.required));休息;}}

在我们创建对象 fields 并最终将对象字段添加到 formGroup 之前,请参阅.使用这个函数,我们直接创建了一个空的 formGroup,我们使用 group.addControl 添加 FormControls 或 FormGroup 或 FormArray.这允许我们在必要时调用递归函数.

所以,在 ngOnInit 中我们制作

 ngOnInit() {this.getForm(this.form, this.fields);}

看到我们可以使用模板变量从父级访问表单

{{dynamic.form?.value|json}}

以及我如何确定表单具有精确"模型 - 在我们有一个带有 {"fields:"exactModel"} 的对象之前-

我们需要一个辅助函数来创建一个 FormArray 的 formGroup

 addGroupArray(fields:any[]){const group:FormGroup=new FormGroup({})fields.forEach(x=>{group.addControl(x.name,new FormControl(null,x.required?Validators.required:null))})返回组}

我们的 FieldBuilderComponent 应该考虑新的两种类型的字段:array";和组".我去把它放在一个字段集中

 <!--在 case group 我们重复使用 field-builder作为getFormGroup"的形式并作为字段field.children"--><fieldset *ngSwitchCase="'group'"><legend>{{field.name}}</legend><field-builder *ngFor="let item of field.children";[form]="getFormGroup(field.name)";[字段]=项目"></field-builder></fieldset><!--在数组的情况下,我们创建一个表并使用该函数getFormArray"--><fieldset *ngSwitchCase="'array'"><legend>{{field.name}}</legend><table [formArrayName]=field.name"><tr><th *ngFor="let item of field.children">{{item.label}}</th><th><button class="btn btn-primary";(click)=getFormArray(field.name).push(this.addGroupArray(field.children))">添加</button></th></tr><tr *ngFor="let group of getFormArray(field.name).controls;let i=index";[formGroupName]=i"><td *ngFor="let item of field.children"><field-builder noLabel="true";[form]="getFormArray(field.name).at(i)";[字段]=项目"></field-builder></td><td><button class="btn btn-primary";(click)=getFormArray(field.name).removeAt(i)">删除</button></td></tr></fieldset>

我使用了两个仅返回casted"的辅助函数;formGroup 和 formArray

 getFormGroup(field:string){返回 this.form.get(field) 作为 FormGroup}getFormArray(字段:字符串){返回 this.form.get(field) 作为 FormArray}

看看我们如何在内部调用自己的组件.我需要添加一个属性"noLabel 不显示标签,以防我们管理 FormArrayLabel

//在构造函数中构造函数(@Attribute('noLabel')noLabel){this.noLabel=noLabel ||错误的;}

并使用

 

我需要在这个组件中再次重复函数addGroupArray(我想不出另一种方式)

好吧,唯一要考虑的是如何将值转换到formArray中,看到一个数组的字段是这样的:

<代码>{类型:数组",名称:数组名称",value:[{firstName:"array",lastName:"array lastName"}],孩子们: [{类型:文本",姓名:名字",标签:名字",要求:真实},{类型:文本",姓名:姓氏",标签:姓氏",要求:真实}]},

通常是 stackblitz 无保修

注意:选项的原子应该是

 

<div class="form-check";*ngFor="let opt of field.options"><label class="form-check-label"><input [formControlName]="field.name";类=表单检查输入"类型=收音机"[值]=opt.key";>{{opt.label}}

更新 真的我从不喜欢拆分"具有真/假值的数组中的一系列复选框.该值更自然,例如c,f"并且选中复选框烹饪".和钓鱼".

好吧,首先是更改原子复选框";使用 [ngModel](ngModelChange).首先我们要创建两个辅助函数:

//一个简单的getter";获取价值获取值(){返回 this.form ?this.form.get(this.field.name).value : null;}//我们通过检查"和钥匙"选项的更改(检查:布尔值,键:任何){const oldvalue = this.form.get(this.field.name).value ||空值;//如果没有值如果(!旧值){//使用带有key"的setValue(如果选中)或 nullthis.form.get(this.field.name).setValue(checked ? "" + key : null);返回;} 别的 {//在值中存储满足条件的所有选项常量值 = 选中?this.field.options.filter(//在旧值中或者是keyx =>oldvalue.indexOf(x.key) >= 0 ||x.key == 键): this.field.options.filter(//在旧值中,不是键x =>oldvalue.indexOf(x.key) >= 0 &&x.key != 键);//如果没有满足的选项,我们给值 null//或键的连接这个.form.get(this.field.name).setValue(value.length > 0 ? value.map(x => x.key).join(",") : null);}}

好吧,现在我们可以使用我们的 [ngModel] 和 (ngModelChange).我们需要说 Angular 是一个独立的"

<div *ngFor="let opt of field.options";class="form-check form-check"><label class="form-check-label"><输入[ngModel]=值&&"value.indexOf(opt.key) >= 0"(ngModelChange)="change($event, opt.key)";[ngModelOptions]={独立:真实}";类=表单检查输入"类型=复选框"id="inlineCheckbox1";值=选项1"/>{{ opt.label }}</label>

因此,我们删除了复选框"的大小写;输入输出函数getForm

 getForm(group: FormGroup, fields: any[]) {for(让 f 个字段){开关(f.type){案例组":...休息;案例数组":....休息;默认:...休息;}}}

I had implemented the dynamic form builder in my project and it is working fine. But, now I need to add a form inside the dynamic form builder. Which will be like form inside a form. So, I had created a formarray inside my ts and it is coming correctly.

I am getting an error like

ERROR Error: Cannot find control with name: '891713'

This is what I had implemented https://angular.io/guide/dynamic-form

Code sample I used is https://stackblitz.com/edit/angular-dynamic-form-builder

So I had created a component named formControl inside the folder 'atoms' and if the control is fromControl, I am again calling field control.

FormControl component

            <td *ngFor="let itm of items; let i = index" formArrayName="childs" >
              <field-builder [field]="itm" [form]="form" ></field-builder>
            </td>

FormControl component Ts


  @Input() field: any = {};
  @Input() form: FormGroup;
  @Output() sendDataToParent = new EventEmitter<string>();
  // form: FormGroup;
  itemLength: any;
  items: any;
  elemData = {}
  row = [];
  isDisabled : boolean = false;
  childs: FormArray;
  formNewStatus : boolean = true;
  // childs: any;
  
  ngOnInit() {
    this.items = this.field.items;
    this.itemLength = this.items.length
    this.items.forEach(element => {
      const elemId =element.id;
      this.elemData[elemId] = ''
    });
    console.log("from control form=====>",this.form)
    this.row.push(this.elemData);
  }

  addTable() {
    const frmArr = this.form.get('childs') as FormArray;
    this.sendDataToParent.emit("add");
    this.row.push(this.elemData)
    console.log("this.row===>",this.row)
  }
  
  deleteRow(x){
    this.row.splice(x, 1 );
  } 
  deleteAllRow(){
    console.log("came here")
    this.row = [];
  }

In the parent component, I had added the code for formarray to add more

  onFormControlValueChange(parentField:any){
    if(parentField === 'add'){
      this.childs = this.form.get('childs') as FormArray;
      this.childs.push(this.fb.group(this.createItem()));
    }
    console.log("this form====>", this.form)
  }

Can anyone help me to solve this issue? Thanks in advance! :)

解决方案

puff, it's a large and complex improve the code to allow fromGroups and formArray, but we are going to try.

First we are going to change a few the DynamicFormBuilderComponent

We are going to create a function getForm to can make recursive create formGroup

  getForm(group: FormGroup, fields: any[]) {
    for (let f of fields) {
      switch (f.type) {
        case "group":
          group.addControl(f.name, new FormGroup({}));
          this.getForm(group.get(f.name) as FormGroup, f.children);

          break;
        case "checkbox":
          group.addControl(f.name, new FormGroup({}));
          const groupOption = group.get(f.name) as FormGroup;
          for (let opt of f.options) {
            groupOption.addControl(opt.key, new FormControl(opt.value));
          }
          break;
        case "array":
          group.addControl(f.name, new FormArray([]));
          const array = group.get(f.name) as FormArray;
          if (f.value)
          {
              f.value.forEach(x=>array.push(this.addGroupArray(f.children)))
              array.patchValue(f.value)
          }

        break;
        default:
          group.addControl(
            f.name,
            new FormControl(f.value || "", Validators.required)
          );
          break;
      }
    }

See that before we create an object fields and finally add the object fields to the formGroup. Using this function we directly create an empty formGroup and we are adding FormControls or FormGroup or a FormArray using group.addControl. This allow us call recursive the function if it's necesary.

So, in ngOnInit we make

  ngOnInit() {
    this.getForm(this.form, this.fields);
  }

See that we can access to the form from parent using a template variable

<dynamic-form-builder #dynamic ..>
{{dynamic.form?.value|json}}

And how I decide that the form has the "exact" model -before we has an object with {"fields:"exactModel"}-

And we need an auxiliar function to create the formGroup of a FormArray

  addGroupArray(fields:any[])
  {
     const group:FormGroup=new FormGroup({})
     fields.forEach(x=>{
       group.addControl(x.name,new FormControl(null,x.required?Validators.required:null))
     })
     return group
  }

Our FieldBuilderComponent sould take account the new two types of fields: "array" and "group". I go to put it inside a fieldset

  <!--in case group we repeat the field-builder using
     as form the "getFormGroup" and as field "field.children"
  -->
  <fieldset *ngSwitchCase="'group'">
     <legend>{{field.name}}</legend>
     <field-builder *ngFor="let item of field.children"
       [form]="getFormGroup(field.name)" [field]="item">
     </field-builder>
  </fieldset>

  <!--in case array we create a table and use the function
      "getFormArray"
  -->

  <fieldset *ngSwitchCase="'array'">
     <legend>{{field.name}}</legend>
      <table [formArrayName]="field.name">
        <tr>
          <th *ngFor="let item of field.children">{{item.label}}</th>
          <th>
          <button class="btn btn-primary" (click)="getFormArray(field.name).push(this.addGroupArray(field.children))">Add</button>
          </th>
        </tr>
        <tr *ngFor="let group of getFormArray(field.name).controls;let i=index" [formGroupName]="i">
           <td *ngFor="let item of field.children">
              <field-builder noLabel="true" [form]="getFormArray(field.name).at(i)"
                [field]="item">
              </field-builder>
            </td>
            <td><button class="btn btn-primary" (click)="getFormArray(field.name).removeAt(i)">Delete</button></td>
        </tr>
      </table>
    </fieldset>

I use two auxiliar function that only return "casted" the formGroup and the formArray

  getFormGroup(field:string)
  {
    return this.form.get(field) as FormGroup
  }
  getFormArray(field:string)
  {
    return this.form.get(field) as FormArray
  }

See how inside we call to the own component. I need add an "attribute" noLabel to not show the label in case we are mannage a FormArrayLabel

  //in constructor
  constructor(@Attribute('noLabel') noLabel) { 
    this.noLabel=noLabel || false;
  }

And use

 <label *ngIf="!noLabel" ....></label>

I need to repeat the function addGroupArray again in this component (I can't imagine another way)

Well, the only to take account is how dive value to the formArray, see that the field of an array is like:

{
  type: "array",
  name: "arrayName",
  value:[{firstName:"array",lastName:"array lastName"}],
  children: [
    {
      type: "text",
      name: "firstName",
      label: "First Name",
      required: true
    },
    {
      type: "text",
      name: "lastName",
      label: "Last Name",
      required: true
    }
  ]
},

As usually this is the stackblitz without warranty

NOTE: the atom for options should be

  <div [formGroup]="form">
    <div class="form-check" *ngFor="let opt of field.options">
      <label class="form-check-label">
      <input [formControlName]="field.name"  class="form-check-input" type="radio" [value]="opt.key" >
        {{opt.label}}
      </label>
    </div>
  </div> 

Update Really I never like "split" a series of checkbox in an array with true/false values. It's more natural that the value was, e.g. "c,f" and is checked the check box "cooking" and "fishing".

Well, the first is change the "atom checkbox" using [ngModel] and (ngModelChange). First we are going to create two auxiliar functions:

  //a simple "getter" to get the value
  get value() {
    return this.form ? this.form.get(this.field.name).value : null;
  }

  //we pass "checked" and the "key" of the option
  change(checked: boolean, key: any) {
    const oldvalue = this.form.get(this.field.name).value || null;

     //if has no value
    if (!oldvalue) {
      //use setValue with the "key" (if checked) or null
      this.form.get(this.field.name).setValue(checked ? "" + key : null);
      return;
    } else {

      //in value store all the options that fullfilled with the condition
      const value = checked
        ? this.field.options.filter( //is in the old value or is the key

            x => oldvalue.indexOf(x.key) >= 0 || x.key == key
          )
        : this.field.options.filter(  //is in the old value and is not the key
            x => oldvalue.indexOf(x.key) >= 0 && x.key != key
          );

      //we give the value null if there're no options that fullfilled
      //or a join of the keys
      this.form
        .get(this.field.name)
        .setValue(value.length > 0 ? value.map(x => x.key).join(",") : null);
    }
  }

Well, now we can use our [ngModel] and (ngModelChange). We need say to Angular that is a "standalone"

<div [formGroup]="form">
      <div *ngFor="let opt of field.options" class="form-check form-check">
        <label class="form-check-label">
          <input
            [ngModel]="value && value.indexOf(opt.key) >= 0"
            (ngModelChange)="change($event, opt.key)"
            [ngModelOptions]="{ standalone: true }"
            class="form-check-input"
            type="checkbox"
            id="inlineCheckbox1"
            value="option1"
          />
          {{ opt.label }}</label>
      </div>
    </div>

So, we remove the case of "checkbox" in out function getForm

  getForm(group: FormGroup, fields: any[]) {
    for (let f of fields) {
      switch (f.type) {
        case "group":
          ...
          break;
        case "array":
           ....
        break;
        default:
          ...
          break;
      }
    }
  }

这篇关于带有formarray的角度动态表单生成器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆