响应式表单验证与ControlValueAccessor无法配合使用。

7
我创建了一个简单的嵌套表单,通过子组件实现而不是将所有内容都放在一个组件中。我已经实现了一个非常基本的ControlValueAccessor,以使其能够以这种方式工作,但是我注意到验证不会在父组件中进行捕获。这意味着即使表单无效,也可以提交错误的表单。
如何将子表单的验证传播到父表单中?
以下是代码。
父级HTML
<form [formGroup]="parentForm" (ngSubmit)="submitData()">
    <h3>Parent</h3>
    <br>
    <ng-container formArrayName="children">
        <ng-container *ngFor="let c of children?.controls; index as j">
            <app-child [formControlName]="j"></app-child>
        </ng-container>
    </ng-container>

    <button mat-raised-button type="button" (click)="addChild()">Add Activity</button>
    <button mat-raised-button type="submit" color="warn" [disabled]="!parentForm.valid">Submit</button>
</form>

<pre>
{{parentForm.valid}} // this is always true because its not getting validator state from children
</pre>
<pre>
{{parentForm.value | json}}
</pre>

父级 TS

export class ParentComponent implements OnInit {
  parentForm: FormGroup;

  constructor(private fb: FormBuilder) {
    
  }

  ngOnInit(): void {
    this.createForm();
  }

  private createForm() {
    this.parentForm = this.fb.group({
      children: this.fb.array([])
    });
  }

  get children(): FormArray {
    return this.parentForm.get("children") as FormArray;
  }

  addChild() {
    const tempChild = new FormControl({
      description: null
    });

    this.children.push(tempChild);
  }

  submitData() {
    console.info(JSON.stringify(this.parentForm.value));
  }
}

子HTML

<form [formGroup]="newChildForm">
    <tr>
        <td>
            <mat-form-field>
                <mat-label>Description</mat-label>
                <input type="text" matInput formControlName="description" required>
            </mat-form-field>
        </td>
    </tr>
</form>

子类型脚本

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ChildComponent),
      multi: true
    }
  ]
})
export class ChildComponent implements ControlValueAccessor, OnInit {
  newChildForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.createForm();
  }

  private createForm() {
    this.newChildForm = this.fb.group({
      description: [null, Validators.required],
    });
  }

  onTouched: () => void = () => { };

  writeValue(value: any) {
    if (value) {
      this.newChildForm.setValue(value, { emitEvent: true });
    }
  }

  registerOnChange(fn: (v: any) => void) {
    this.newChildForm.valueChanges.subscribe((val) => {
      fn(val);
    });
  }

  setDisabledState(disabled: boolean) {
    disabled ? this.newChildForm.disable() : this.newChildForm.enable();
  }

  registerOnTouched(fn: () => void) {
    this.onTouched = fn;
  }

  ngOnInit(): void {  }
}

Stackblitz链接

编辑2:嵌套方法

带有验证器和变更检测的Stackblitz链接


你想在 https://stackblitz.com/ 这个 angular editor 上分享你的 Angular 代码吗? - Suneel Kumar
1个回答

8

您可以通过在子组件中实现Validator接口来实现此功能,这需要您在子组件中编写validate方法,在每次值更改时进行检查。以下是示例代码:

@Component({
  selector: "app-child",
  templateUrl: "./child.component.html",
  styleUrls: ["./child.component.css"],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ChildComponent),
      multi: true,
    },
    {
      // >>>>>> 1- The NG_VALIDATORS should be provided here <<<<<
      provide: NG_VALIDATORS,
      useExisting: ChildComponent,
      multi: true,
    },
  ],
})
export class ChildComponent implements ControlValueAccessor, Validator, OnInit {
  newChildForm: FormGroup;

  onChange: any = () => {};
  onValidationChange: any = () => {};

  constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {
    this.createForm();
  }

  private createForm() {
    this.newChildForm = this.fb.group({
      description: [null, Validators.required],
      children: this.fb.array([]),
    });
  }

  onTouched: () => void = () => {};

  writeValue(value: any) {
    if (value) {
      this.newChildForm.setValue(value, { emitEvent: true });
    }
  }

  registerOnChange(fn: (v: any) => void) {
    this.onChange = fn;
  }

  setDisabledState(disabled: boolean) {
    disabled ? this.newChildForm.disable() : this.newChildForm.enable();
  }

  registerOnTouched(fn: () => void) {
    this.onTouched = fn;
  }

  ngOnInit(): void {
    this.newChildForm.valueChanges.subscribe((val) => {
      this.onChange(val);

      // >>>> 4- call registerOnValidatorChange after every changes to check validation of the form again <<<<
      this.onValidationChange();
    });
  }

  // >>>> 2- validate method should be added here <<<<
  validate(): ValidationErrors | null {
    if (this.newChildForm?.invalid) {
      return { invalid: true };
    } else {
      return null;
    }
  }

  // >>>> 3- add registerOnValidatorChange to call it after every changes <<<<
  registerOnValidatorChange?(fn: () => void): void {
    this.onValidationChange = fn;
  }

  get children(): FormArray {
    return this.newChildForm.get("children") as FormArray;
  }

  addChild() {
    const tempChild = new FormControl({
      description: null,
      children: [],
    });

    this.children.push(tempChild);
    this.cdr.detectChanges();
  }
}

1
那么我在哪里实现它?你能在我发布的代码中演示一下吗? - Aeseir
我已经更新了我的答案,并对您的子组件进行了必要的更改。 请注意 >>>>> 1-2-3 <<<< 的更改。 - Amer
我已经尝试过了,部分成功了,但是现在它抛出了一个错误:ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'。 - Aeseir
请您能否创建一个stackblitz,这样我们就可以帮助您找出问题所在了? - Amer
1
那个在 StackBlitz 上可以运行,但在本地无法运行。我已经接受了你的答案,现在会深入研究我的代码。感谢你的帮助。 - Aeseir
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接