响应式表单,子组件的 FormArray 返回错误

Reactive Form, with FormArray of child components returning error

我正在研究如何将复杂的表单重构为更小的表单。

本质上有一个父表单,它有一个子组件数组,其中包含它们的所有者表单。

父 TS

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  form: FormGroup = this.fb.group({
    mainAddress: [null, Validators.required],
    addresses: this.fb.array([]),
  });
  constructor(private fb: FormBuilder) {}

  get addresses() {
    return this.form.controls['addresses'] as FormArray;
  }
  removeAddress(i: number) {
    this.addresses.removeAt(i);
  }

  addAddress() {
    this.addresses.push(
      this.fb.control({
        address: [null, Validators.required],
      })
    );
  }
}
<form [formGroup]="form">
  <app-address-form formControlName="mainAddress"></app-address-form>
  <hr />
  <ng-container formArrayName="addresses">
    <ng-container *ngFor="let addressForm of addresses.controls; index as i">
      <app-address-form [formControlName]="i"></app-address-form>
      <button (click)="removeAddress(i)">Remove Address</button>
    </ng-container>
  </ng-container>
</form>
<button (click)="addAddress()">Add Address</button>

子地址 TS

@Component({
  selector: 'app-address-form',
  templateUrl: './address-form.component.html',
  styleUrls: ['./address-form.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: AddressFormComponent,
    },
    {
      provide: NG_VALIDATORS,
      multi: true,
      useExisting: AddressFormComponent,
    },
  ],
})
export class AddressFormComponent
  implements ControlValueAccessor, OnDestroy, Validator
{
  @Input()
  legend: string;

  form: FormGroup = this.fb.group({
    addressLine1: [null, [Validators.required]],
    addressLine2: [null, [Validators.required]],
    zipCode: [null, [Validators.required]],
    city: [null, [Validators.required]],
  });

  onTouched: Function = () => {};

  onChangeSubs: Subscription[] = [];

  constructor(private fb: FormBuilder) {}

  ngOnDestroy() {
    for (let sub of this.onChangeSubs) {
      sub.unsubscribe();
    }
  }

  registerOnChange(onChange: any) {
    const sub = this.form.valueChanges.subscribe(onChange);
    this.onChangeSubs.push(sub);
  }

  registerOnTouched(onTouched: Function) {
    this.onTouched = onTouched;
  }

  setDisabledState(disabled: boolean) {
    if (disabled) {
      this.form.disable();
    } else {
      this.form.enable();
    }
  }

  writeValue(value: any) {
    if (value) {
      console.log(value);
      this.form.setValue(value, { emitEvent: false });
    }
  }

  validate(control: AbstractControl) {
    if (this.form.valid) {
      return null;
    }

    let errors: any = {};

    errors = this.addControlErrors(errors, 'addressLine1');
    errors = this.addControlErrors(errors, 'addressLine2');
    errors = this.addControlErrors(errors, 'zipCode');
    errors = this.addControlErrors(errors, 'city');

    return errors;
  }

  addControlErrors(allErrors: any, controlName: string) {
    const errors = { ...allErrors };

    const controlErrors = this.form.controls[controlName].errors;

    if (controlErrors) {
      errors[controlName] = controlErrors;
    }

    return errors;
  }
}

子地址HTML

<fieldset [formGroup]="form">
  <mat-form-field>
    <input
      matInput
      placeholder="Address Line 1"
      formControlName="addressLine1"
      (blur)="onTouched()"
    />
  </mat-form-field>
  <mat-form-field>
    <input
      matInput
      placeholder="Address Line 2"
      formControlName="addressLine2"
      (blur)="onTouched()"
    />
  </mat-form-field>
  <mat-form-field>
    <input
      matInput
      placeholder="Zip Code"
      formControlName="zipCode"
      (blur)="onTouched()"
    />
  </mat-form-field>
  <mat-form-field>
    <input
      matInput
      placeholder="City"
      formControlName="city"
      (blur)="onTouched()"
    />
  </mat-form-field>
</fieldset>
Error i get is : 

    Error: NG01002: Must supply a value for form control with name: 'addressLine1'

请记住,我正在尝试通过尽可能多地分离(DRY 原则)来避免代码重复。

感谢任何反馈

这有一个类似的解决方案 here

如果您在 FormGroup 上使用 setValue() 方法,您将需要更新该组中的所有控件。如果您只更新单个控件,则需要改用 patchValue()。否则你会得到 NG01002 错误。

改变这个:

  writeValue(value: any) {
    if (value) {
      console.log(value);
      this.form.setValue(value, { emitEvent: false });
    }
  }

为此,将要更新的控件添加为参数:

  writeValue(controlToUpdate, value: any) {
    if (value) {
      console.log(value);
      this.form.patchValue({controlToUpdate: value},{emitEvent: false});
    }
  }

希望这对您有所帮助。

您有一个 FormControls (*) 数组(每个 FormControl“存储”一个具有以下属性的复杂对象:addressLine1、addressLine2、zipCode 和 city,但 FormArray 仍然是一个FormControls 的 FormArray)

所以,你的函数 addAddress 应该是这样的

  addAddress() {
    this.addresses.push(this.fb.control(null))
  }

(*)当我们使用嵌套表单时存在两种方法。一种是您的方法:创建一个存储复杂对象的自定义 formControl,并在组件内部创建“sub-formGroup”。另一个是在主组件中创建“sub-formGroup”并将 formGroup 传递给一个组件(在最后一种情况下,您不创建实现 ControlValueAccess 的自定义表单控件,否则是一个带有 @Input[= 的简单组件16=]

更新 关于 person.component 和 adress.component

中的函数验证

在person.component中你可以创建一个函数验证像

control:AbstractControl //<--if you want to see the error
                        //in .html you can use
                        //{{control.errors|json}}

validate(control: AbstractControl) {

    if (!this.control) this.control = control; //<--this line is only if
                                               //we can "get" the control

    if (this.form.valid)
      return null;

    //if only give a generic error
    //return {error:'form not fullfilled'}

    let errors: any = null;
    Object.keys(this.form.controls).forEach((e: any) => {

      //see that this.adresses is a FormArray, the formArray has no errors
      //else his elements

      if (e == 'addresses') {
        this.addresses.controls.forEach((x,i) => {
          const err = x.errors;
          if (err) {
            errors = errors || {};
            errors[e+'.'+i] = err;
          }
        });
      } else {
        const err = this.form.get(e).errors;

        if (err) {
          errors = errors || {};
          errors[e] = err;
        }
      }
    });
    return errors;
  }
}

当您添加地址时使用 updateValueAndValidity

  addAddress() {
    this.addresses.push(this.fb.control(null));
    this.cdr.detectChanges();
    this.form.updateValueAndValidity();
  }

函数验证 adress.form 喜欢

  validate(control: AbstractControl) {
    if (!this.control) this.control = control;
    if (this.form.valid) return null;

    //if only give a generic error
    //return {error:'form not fullfilled'}

    let errors: any = null;
    Object.keys(this.form.controls).forEach((e: any) => {
      const err = this.form.get(e).errors;
      if (err) {
        errors = errors || {};
        errors[e] = err;
      }
    });

    return errors;
  }

你的forked stackblitz