nested custom FormArray component doesn't bind with child form with FormArrayName

时间:2019-04-08 13:18:25

标签: angular angular-reactive-forms controlvalueaccessor

I tried to have 2 nested forms using CVA. the problem is the second from isn't initialized with data when I bind it to a formControl.

Stackblitz

enter image description here

I have MAIN-FORM:

this.requestForm = this.fb.group({
  garageId: 0,
  routes: new FormArray([
    new FormGroup({
      addressPointId: new FormControl,
      municipalityId: new FormControl,
      regionId: new FormControl,
      rvId: new FormControl,
      sequenceNumber: new FormControl,
      settlementId: new FormControl,
      regionName: new FormControl,
      municipalityName: new FormControl,
      settlementName: new FormControl,
      description: new FormControl,
    })
  ]),
  endDateTime: 0,
});

In main-form html I bind routes to with formArrayName.

 <app-cva-form-array formArrayName="routes"></app-cva-form-array>

Component CVA-FORM-ARRAY has.

form = new FormArray([
new FormGroup({
  addressPointId: new FormControl,
  municipalityId: new FormControl,
  regionId: new FormControl,
  rvId: new FormControl,
  sequenceNumber: new FormControl,
  settlementId: new FormControl,
  regionName: new FormControl,
  municipalityName: new FormControl,
  settlementName: new FormControl,
  description: new FormControl,
})
]);

Everything from here works just fine. I bind each formGroup in the array to child component CVA-FORM.

<app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>

CVA-FORM for each formGroup I created separate component in case I want to use component itself not the whole array.

  form: FormGroup = new FormGroup({
    regionName: new FormControl,
    regionId: new FormControl,
    municipalityName: new FormControl,
    municipalityId: new FormControl,
    sequenceNumber: new FormControl,
    settlementName: new FormControl,
    settlementId: new FormControl,
    addressPointId: new FormControl,
    description: new FormControl,
    rvId: new FormControl,
  });

the main-form <--to--> app-cva-form-array binding doesn't work for some reason.

The idea of these forms comes from kara's talk on angulaconnect. here are her slides.

help plz!

3 个答案:

答案 0 :(得分:2)

当您使用“自定义表单控件”时,需要考虑到您将带有表单控件(不是FormArray而不是FormGroup)的cursom表单控件。 FormControl具有一个数组或对象作为值,但是您不必对此感到困惑。(*)

您可以在stackblitz的工作中看到

那就像你的表格一样

//in main.form
this.requestForm = new FormGroup({
  garageId: new FormControl(0),
  routes: new FormControl(routes), //<--routes will be an array of object
  endDateTime: new FormControl(0)
})

//in cva-form-array
this.form=new FormArray([new FormControl(...)]); //<-this.form is a 
                             //formArray of FormControls NOT of formGroup

//finally in your cva-form
this.form=new FormGroup({});
this.form=formGroup({
      addressPointId: new FormControl(),
      municipalityId: new FormControl(),
      ...
})

我已经创建了一个const以导出为简单的代码。我的const expor是

export const dataI = {
  addressPointId: "",
  municipalityId: "",
  regionId: "",
  rvId: "",
  sequenceNumber: "",
  settlementId: "",
  regionName: "",
  municipalityName: "",
  settlementName: "",
  description: "",
}

所以,在mainForm中,我们有

  ngOnInit() {
    let routes:any[]=[];
    routes.push({...dataI});
    this.requestForm = new FormGroup({
      garageId: new FormControl(0),
      routes: new FormControl(routes),
      endDateTime: new FormControl(0)
    })
  }
<mat-card [formGroup]="requestForm" style="background: #8E8D8A">
    <app-cva-form-array formControlName="routes"></app-cva-form-array>
</mat-card>

在cvc-form数组中,当我们赋值时创建formArray

  writeValue(v: any) {
    this.form=new FormArray([]);
    for (let value of v)
        this.form.push(new FormControl(value))

    this.form.valueChanges.subscribe(res=>
    {
      if (this.onChange)
        this.onChange(this.form.value)
    })
  }

    <form [formGroup]="form" >
        <mat-card *ngFor="let route of form.controls; 
            let routeIndex = index; let routeLast = last;">
           <button (click)="deleteRoute(routeIndex)">
             cancel
           </button>
           <app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>
      </form>

最后是cva形式

  writeValue(v: any) {
    this.form=new FormGroup({});
    Object.keys(dataI).forEach(x=>{
      this.form.addControl(x,new FormControl())
    })

    this.form.setValue(v, { emitEvent: false });
    this.form.valueChanges.subscribe(res=>{
       if (this.onChanged)
        this.onChanged(this.form.value)
    })
  }

<div [formGroup]="form">
  <mat-form-field class="locationDate">
    <input formControlName="regionName">
    <mat-autocomplete #region="matAutocomplete" 
      (optionSelected)="selectedLocation($event)">
      <mat-option *ngFor="let region of regions" 
      [value]="region">
        {{region.regionName}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
  <mat-form-field class="locationDate">
    <input formControlName="municipalityName" 
      [matAutocomplete]="municipality"
      (blur)="onTouched()"
      [readonly]="checked || this.form.value.regionId < 1">
   ....
   </form>

(*)是的,我们习惯于看到FormControl具有一个字符串或数字作为值,但是没有人禁止我们将该值作为对象或数组(例如ng-bootstrap DatePicker存储一个对象{year:.. month:..,day ..},mat-multiselect存储数组,...)

更新当然,我们可以向控件提供来自服务或类似服务的数据。我们唯一需要考虑的就是我们如何提供数据。通常,我喜欢创建一个接收数据或为null并返回FormControl的函数

  getForm(data: any): FormGroup {
    data = data || {} as IData;
    return new FormGroup({
      garageId: new FormControl(data.garageId),
      routes: new FormControl(data.routes),
      endDateTime: new FormControl(data.endDateTime)
    })
  }

其中IData是接口

export interface IData {
  garageId: number;
  routes: IDetail[];
  endDateTime: any
}

和IDetail另一个界面

export interface IDetail {
  addressPointId: string;
  ...
  description: string;
}

然后我们可以有一个复杂的数据,例如(大对象很抱歉)

let data = {
  garageId: 1,
  routes: [{
    addressPointId: "adress",
    municipalityId: "municipallyty",
    regionId: "regionId",
    rvId: "rvId",
    sequenceNumber: "sequenceNumber",
    settlementId: "settlementId",
    regionName: "regionName",
    municipalityName: "municipalityName",
    settlementName: "settlementName",
    description: "description",
  },
  {
    addressPointId: "another adress",
    municipalityId: "another municipallyty",
    regionId: "another regionId",
    rvId: "another rvId",
    sequenceNumber: "another sequenceNumber",
    settlementId: "another settlementId",
    regionName: "another regionName",
    municipalityName: "another municipalityName",
    settlementName: "another settlementName",
    description: "another description",
  }],
  endDateTime: new Date()
}

然后只需要

this.requestForm = this.getForm(data);

堆叠闪电战(如果更新)

答案 1 :(得分:1)

我认为这里的问题是formArrayName不是NG_VALUE_ACCESSOR/DefaultValueAccessor的输入。

也请注意:

  

她的示例是静态的parent->multiple children ...意味着1对很多,而不是动态的。您正在尝试静态   parent到由child->grandChild建立的许多动态formArray关系,然后尝试将grandChild形式动态链接到通过formArrayIndex传递的父childgrandChild。您的堆叠突飞猛进偏离了她所教的结构,并且肯定会引入一些讲座中未涵盖的新挑战。

探索如何在FormArray级别上遍历parent并从该循环中实例化child->grandchild关系是一种可能的解决方案,这样您就不会传递整个数组向下,只有适用的formGroup

<h1>MAIN FORM</h1>
    {{ requestForm.value | json }}
    <div *ngFor="let route of requestForm.get('routes').controls">
        <app-cva-form-array formControl="route" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>
    </div>

选择器

  • 输入:不([type = checkbox]) [formControlName]
  • textarea [formControlName]
  • 输入:不([type = checkbox]) [formControl]
  • textarea [formControl]
  • 输入:不([type = checkbox]) [ngModel]
  • textarea [ngModel]
  • [ngDefaultControl]

https://angular.io/api/forms/DefaultValueAccessor#selectors


您唯一的输入选项是formControlNameformControlngModelngDefaultControl ...

  

这是formArrayNamemain-form <--> cva-form-array中将不能工作的原因,但是,formControl 用于   child-child to child level,因为您传递的是单数   从您的formControl app-cva-form到您的app-cva-form-array   通过*ngFor循环。

<mat-card *ngFor="let route of getForm.controls;
   <app-cva-form [formControl]="route" (blur)="onTouched()"></app-cva-form>

我相信这里要理解的关键是formArray只是其子级的组织容器……在这种情况下,如果没有其他逻辑的帮助,它将无法完成您想要的操作。

  

目前似乎没有必要的功能   接受formArray作为输入,迭代/动态管理数组,   并将链接更改返回到上级formArray

答案 2 :(得分:0)

您需要将更新的表单数据从子组件传递到父组件。我使用了this.form.valueChanges()方法来检测更改,然后将Form值发送给父组件。

  

父项:

HTML代码:

<app-cva-form-array formArrayName="routes" (onChildFormValueChange)="onFormChange($event)"></app-cva-form-array>

TS代码:

public onFormChange(form): void {
    this.requestForm = form;
}
  

子组件:

HTML代码:

No change:)

TS代码:

@Output() onChildFormValueChange: EventEmitter<any> = new EventEmitter<any>();

registerEvent() {
    this.form.valueChanges.subscribe(() => {
      this.onFormValueChange()
    });
}

public onFormValueChange(): void {
    this.onChildFormValueChange.emit(this.form);
}

并在构造函数中调用registerEvent方法,例如:

constructor(){
  this.registerEvent();
}

Working_Stackblitz