Angular 反应式表单自定义控件异步验证 [英] Angular reactive form custom control async validation

查看:39
本文介绍了Angular 反应式表单自定义控件异步验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新:异步验证问题已成功解决.但是初始验证状态还有另一个问题.查看最新答案.

诀窍如下:

  • 具有实现 ControlValueAccessor 接口的组件可用作自定义控件.
  • 这个组件在一些反应式表单中用作 FormControl.
  • 此自定义控件具有异步验证器.

问题:

来自 ControlValueAccessor 接口的方法 validate() 在值更改后立即调用并且不等待异步验证器.当然,控制无效且待定(因为正在进行验证)并且主表单也无效且待定.一切正常.

但是.当异步验证器完成验证并返回空值(表示值有效)时,自定义控件将有效并且状态也变为有效,但父控件仍然无效且处于挂起状态,因为值访问器的 validate() 没有再次调用.

我试图从 validate() 方法返回 observable,但主表单将其解释为错误对象.

我找到了解决方法:当异步验证器完成验证时,从自定义控件传播更改事件.它迫使主窗体再次调用 validate() 方法并获得正确的有效状态.但看起来又脏又粗糙.

问题是:必须做什么才能使父表单由子自定义控件的异步验证器管理?必须说它适用于同步验证器.

所有项目代码都可以在这里找到:https://stackblitz.com/edit/angular-fdcrbl

主表单模板:

<child-control formControlName="childControl"></child-control></表单>

主窗体类:

import { Component, OnInit } from "@angular/core";import { FormBuilder, FormGroup } from "@angular/forms";@成分({选择器:我的应用程序",模板网址:./app.component.html"})导出类 AppComponent 实现 OnInit {mainForm:FormGroup;构造函数(私有 formBuilder:FormBuilder){}ngOnInit() {this.mainForm = this.formBuilder.group({childControl: this.formBuilder.control("")});}}

自定义子控件模板:

<div class="form-group"><label translate>子控件:</label><输入类型=文本"formControlName=childControl">

自定义子控件类:

import { Component, OnInit } from "@angular/core";import { AppValidator } from "../app.validator";进口 {表单组,异步验证器,表单生成器,NG_VALUE_ACCESSOR,NG_ASYNC_VALIDATORS,验证错误,控制值存取器来自@angular/forms";import { Observable } from "rxjs";import { map, first } from "rxjs/operators";@成分({templateUrl: "./child-control.component.html",选择器:子控件",供应商: [{提供:NG_VALUE_ACCESSOR,使用现有:ChildControlComponent,多:真},{提供:NG_ASYNC_VALIDATORS,使用现有:ChildControlComponent,多:真}]})导出类 ChildControlComponent实现 ControlValueAccessor、AsyncValidator、OnInit {childForm:FormGroup;构造函数(私有表单构建器:FormBuilder,私有应用验证器:AppValidator) {}ngOnInit() {this.childForm = this.formBuilder.group({childControl: this.formBuilder.control(",[],[this.appValidator.asyncValidation()])});this.childForm.statusChanges.subscribe(status => {控制台日志(订阅",状态);});}//区域 CVA公开 onTouched: () =>void = () =>{};writeValue(val: any): void {如果(!val){返回;}this.childForm.patchValue(val);}registerOnChange(fn: () => void): void {this.childForm.valueChanges.subscribe(fn);}registerOnTouched(fn: () => void): void {this.onTouched = fn;}setDisabledState?(isDisabled: boolean): void {被禁用 ?this.childForm.disable() : this.childForm.enable();}validate(): Observable{console.log('验证');//返回 this.taxCountriesForm.valid ?空:{无效:真实};返回 this.childForm.statusChanges.pipe(地图(状态 => {console.log('pipe', status);返回状态 == VALID"?空:{无效:真实};}),);}//末端区域}

解决方案

有了这样的验证,一切都很容易.只需在最后添加 first()/take(1) 即可关闭 observable 并完成订阅.

 validate(): Observable{返回 this.childForm.statusChanges.pipe(filter((status) => status !== 'PENDING'),地图(状态 => {返回状态 == VALID"?空:{无效:真实};}),第一的(),);}

但还有一个问题.

statusChange 没有在 init 组件上传播状态更改.Validate 方法已按预期调用,statusChange observable 返回,但异步验证完成后什么也没发生.因此,父表单仍处于 PENDING 状态.

但是如果您手动执行此操作(只需更改输入值),validate 方法中的 statusChange 将正确触发并返回正确的状态.

如果能在这方面得到一些帮助就好了.

这是更新的代码示例:https://stackblitz.com/edit/angular-cva-async-validation

UPDATE: Issue with async validation successfully solved. But there is another issue with initial validation state. See latest answer.

Here is the trick:

  • Have component with implemented ControlValueAccessor interface to be used as custom control.
  • This component used as FormControl inside some reactive form.
  • This custom control has async validator.

The problem:

Method validate() from ControlValueAccessor interface calls right after value change and do not wait async validator. Of course control is invalid and pending (because validation in progress) and main form also goes to be invalid and pending. Everything is okay.

But. When async validator finish to validate and return null (means value is valid) then custom control going to be valid and status changes to valid also, but parent from still invalid with pending status because validate() from value accessor haven't called again.

I have tried to return observable from the validate() method, but main form interprets it as error object.

I found workaround: propagate change event from custom control when async validator finish to validate. It's forcing main form to call validate() method again and get correct valid status. But it looks dirty and rough.

Question is: What have to be done to make parent form be managed by async validator from child custom control? Must say it works great with sync validators.

All project code can be found here: https://stackblitz.com/edit/angular-fdcrbl

Main form template:

<form [formGroup]="mainForm">
    <child-control formControlName="childControl"></child-control>
</form>

Main form class:

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html"
})
export class AppComponent implements OnInit {
  mainForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.mainForm = this.formBuilder.group({
      childControl: this.formBuilder.control("")
    });
  }
}

Custom child control template:

<div [formGroup]="childForm">
    <div class="form-group">
        <label translate>Child control: </label>
        <input type="text" formControlName="childControl">
    </div>
</div>

Custom child control class:

import { Component, OnInit } from "@angular/core";
import { AppValidator } from "../app.validator";
import {
  FormGroup,
  AsyncValidator,
  FormBuilder,
  NG_VALUE_ACCESSOR,
  NG_ASYNC_VALIDATORS,
  ValidationErrors,
  ControlValueAccessor
} from "@angular/forms";
import { Observable } from "rxjs";
import { map, first } from "rxjs/operators";

@Component({
  templateUrl: "./child-control.component.html",
  selector: "child-control",
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: ChildControlComponent,
      multi: true
    },
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: ChildControlComponent,
      multi: true
    }
  ]
})
export class ChildControlComponent
  implements ControlValueAccessor, AsyncValidator, OnInit {
  childForm: FormGroup;

  constructor(
    private formBuilder: FormBuilder,
    private appValidator: AppValidator
  ) {}

  ngOnInit() {
    this.childForm = this.formBuilder.group({
      childControl: this.formBuilder.control(
        "",
        [],
        [this.appValidator.asyncValidation()]
      )
    });
    this.childForm.statusChanges.subscribe(status => {
      console.log("subscribe", status);
    });
  }

  // region CVA
  public onTouched: () => void = () => {};

  writeValue(val: any): void {
    if (!val) {
      return;
    }
    this.childForm.patchValue(val);
  }

  registerOnChange(fn: () => void): void {
    this.childForm.valueChanges.subscribe(fn);
  }

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

  setDisabledState?(isDisabled: boolean): void {
    isDisabled ? this.childForm.disable() : this.childForm.enable();
  }

  validate(): Observable<ValidationErrors | null> {
    console.log('validate');
    // return this.taxCountriesForm.valid ? null : { invalid: true };
    return this.childForm.statusChanges.pipe(
      map(status => {
        console.log('pipe', status);
        return status == "VALID" ? null : { invalid: true };
      }),
    );
  }
  // endregion
}

解决方案

Everything is easy with such validation. Just need to add first()/take(1) at the end to close observable and finish subscription.

 validate(): Observable<ValidationErrors | null> {
    return this.childForm.statusChanges.pipe(
      filter((status) => status !== 'PENDING'),
      map(status => {
        return status == "VALID" ? null : { invalid: true };
      }),
      first(),
    );
  }

But there is another issue.

statusChange haven't propagate status changes on init component. Validate method have been called as expected, statusChange observable returned, but nothing happened when async validation finished. As a result parent form still in PENDING state.

But if you do that manually (just change the input value) statusChange inside validate method triggers correctly and returns correct status.

It would be nice to get some help with this.

Here is updated code example: https://stackblitz.com/edit/angular-cva-async-validation

这篇关于Angular 反应式表单自定义控件异步验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆