从父组件动态设置子表单验证器

Dynamically Set SubForm Validators from the Parent component

我一直在 Angular 8 中创建可重用的子表单,使用 FormGroupDirective 为子项获取父项 FormGroup 的引用很容易完成,但是我很难弄清楚如何从父级动态设置子窗体上的验证器。

使用 ngAfterViewInit 我可以看到子表单的表单控件已添加到父表单,但我似乎无法直接从父表单更新默认的子表单验证器。

// PARENT COMPONENT
public ngAfterViewInit() {
  // Form controls have been applied to the form group
  console.log('ngAfterViewInit', this.form.value);

  // BUT the validators of profile form aren't being set?
  this.form.get('profile').get('description').setValidators([Validators.required]);
}

我创建了 StackBlitz 我的示例代码。除配置文件描述外的所有字段都是必填字段,并具有默认值。因此,如果不对验证器进行任何更改,表单将在提交时通过验证并在控制台中输出 VALID。在 ngAfterViewInit 中,我将描述字段验证器设置为必需,这应该可以防止提交表单并在控制台中输出 INVALID,但它仍然提交并输出 有效.

是否可以动态设置子表单的验证器而不通过 @Input 绑定传递它们?

显然,您只需在已更改的表单控件上使用 updateValueAndValidity

this.form.get('profile').get('description').updateValueAndValidity();

所以最后看起来像这样:

public ngAfterViewInit() {
  // Form controls will now exist
  console.log('ngAfterViewInit', this.form.value);

  // BUT the validators of the profile form should not allow submission if set to required, and they do?

  this.form.get('profile').get('description').setValidators([Validators.required]);
  this.form.get('profile').get('description').updateValueAndValidity();
}

执行此操作时,您会在控制台中收到一条错误消息:

ERROR
Error: ExpressionChangedAfterItHasBeenCheckedError: 
Expression has changed after it was checked. 
Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'.

可以通过使用 ChangeDetectorRef 并调用 this.changeDetector.detectChanges() 或将父组件的更改检测策略更新为 ChagneDetectionStrategy.OnPush.

来解决这个问题
constructor(private changeDetector: ChangeDetectorRef) { }

// Removed for brevity

public ngAfterViewInit() {
  const descControl = this.form.get('profile').get('description');
  descControl.setValidators([Validators.required]);
  descControl.updateValueAndValidity();

  this.changeDetector.detectChanges();
}
@Component({
  selector: 'app-page',
  templateUrl: './page.component.html',
  styleUrls: ['./page.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageComponent implements OnInit, AfterContentInit, AfterViewInit {
  // Removed for brevity

  public ngAfterViewInit() {
    const descControl = this.form.get('profile').get('description');
    descControl.setValidators([Validators.required]);
    descControl.updateValueAndValidity();
  }
}