带有formarray的角度动态表单生成器

时间:2021-04-15 06:15:58

标签: angular angular11

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

我收到类似的错误

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

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

我使用的代码示例是 https://stackblitz.com/edit/angular-dynamic-form-builder

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

FormControl 组件

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

FormControl 组件 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 = [];
  }

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

  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)
  }

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

1 个答案:

答案 0 :(得分:2)

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

首先我们要更改一些 DynamicFormBuilderComponent

我们将创建一个函数 getForm 来递归创建 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;
      }
    }

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

所以,在 ngOnInit 中我们制作

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

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

<dynamic-form-builder #dynamic ..>
{{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))
     })
     return group
  }

我们的 FieldBuilderComponent 应该考虑新的两种类型的字段:“array”和“group”。我去把它放在一个字段集中

  <!--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>

我使用两个辅助函数,它们只返回“强制转换”formGroup 和 formArray

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

看看我们如何在内部调用自己的组件。我需要添加一个“属性” noLabel 以在我们管理 FormArrayLabel 的情况下不显示标签

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

并使用

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

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

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

{
  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
    }
  ]
},

通常是the stackblitz没有保修

注意:选项的原子应该是

  <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> 

更新 真的,我从不喜欢用真/假值在数组中“拆分”一系列复选框。该值更自然,例如"c,f" 并选中复选框 "cooking" 和 "fishing"。

嗯,首先是使用 [ngModel](ngModelChange) 更改“原子复选框”。首先我们要创建两个辅助函数:

  //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);
    }
  }

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

<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>

因此,我们在函数 getForm 中删除了“checkbox”的情况

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