2

我尝试使用 CVA 制作 2 个嵌套表单。问题是当我将第二个 from 绑定到 formControl 时,它没有用数据初始化。

堆栈闪电战

在此处输入图像描述

我有主窗体

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

在主表单 html 中,我将路由绑定到 formArrayName。

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

组件CVA-FORM-ARRAY有。

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,
})
]);

这里的一切都很好。我将数组中的每个 formGroup 绑定到子组件 CVA-FORM。

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

CVA-FORM 为每个 formGroup 我创建了单独的组件,以防我想使用组件本身而不是整个数组。

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

main-form <--to--> app-cva-form-array 绑定由于某种原因不起作用。

这些形式的想法来自kara 关于 angulaconnect 的演讲。 这是她的幻灯片。

帮助PLZ!

4

3 回答 3

11

当您使用“自定义表单控件”时,您需要考虑使用表单控件(不是 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 出口是

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

然后只需要make

this.requestForm = this.getForm(data);

堆栈闪电战(如果更新)

于 2019-04-14T11:51:52.817 回答
1

我相信这里的问题是这formArrayName不是NG_VALUE_ACCESSOR/DefaultValueAccessor.

另请注意:

她的例子是静态的parent->multiple children……意思是一对多而不是动态的。您正在尝试 从 . 构建的parent许多动态关系的静态关系,然后尝试将表单动态链接到通过. 你的 stackblitz 偏离了她所教的结构,并且肯定会引入一些讲座中没有涉及的新挑战。child->grandChildformArraygrandChildformArrayIndexchildgrandChild

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>

选择器

  • 输入:not([type=checkbox]) [formControlName]
  • 文本区域[formControlName]
  • 输入:not([type=checkbox]) [formControl]
  • 文本区域[表单控件]
  • 输入:not([type=checkbox]) [ngModel]
  • 文本区域[ngModel]
  • [ngDefaultControl]

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


输入的唯一选项是formControlNameformControlngModel...ngDefaultControl

这就是为什么不会formArrayName起作用的原因,但是起作用 ,因为你正在通过循环将单数传递给 你的, 。main-form <--> cva-form-arrayformControl child-child to child levelformControlapp-cva-formapp-cva-form-array*ngFor

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

我相信这里要理解的关键是它formArray只是其子级的组织容器......在这种情况下,如果没有额外逻辑的帮助,它不会做你想要的事情。

目前似乎没有必要的功能来接受formArray作为输入、迭代/动态管理数组以及将更改链接回 parent formArray

于 2019-04-11T15:41:17.903 回答
-1

您需要将更新的表单数据从子组件传递给父组件。我已经使用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

于 2019-04-10T08:07:21.543 回答