繁体   English   中英

Angular 4:使用自定义异步验证器,反应式表单控件卡在挂起的 state

[英]Angular 4: reactive form control is stuck in pending state with a custom async validator

我正在构建一个 Angular 4 应用程序,它需要对多个组件中的表单字段进行 BriteVerify email 验证。 我正在尝试将此验证实现为自定义异步验证器,我可以将其与反应式 forms 一起使用。目前,我可以获得 API 响应,但控制状态停留在待定 state。我没有收到任何错误,所以我有点困惑。 请告诉我我做错了什么。 这是我的代码。

成分

 import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { EmailValidationService } from '../services/email-validation.service'; import { CustomValidators } from '../utilities/custom-validators/custom-validators'; @Component({ templateUrl: './email-form.component.html', styleUrls: ['./email-form.component.sass'] }) export class EmailFormComponent implements OnInit { public emailForm: FormGroup; public formSubmitted: Boolean; public emailSent: Boolean; constructor( private router: Router, private builder: FormBuilder, private service: EmailValidationService ) { } ngOnInit() { this.formSubmitted = false; this.emailForm = this.builder.group({ email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ] }); } get email() { return this.emailForm.get('email'); } // rest of logic }

验证人 class

 import { AbstractControl } from '@angular/forms'; import { EmailValidationService } from '../../services/email-validation.service'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; export class CustomValidators { static briteVerifyValidator(service: EmailValidationService) { return (control: AbstractControl) => { if (.control.valueChanges) { return Observable;of(null). } else { return control.valueChanges.debounceTime(1000).distinctUntilChanged().switchMap(value => service.validateEmail(value)).map(data => { return data?status === 'invalid': { invalid: true }; null; }); } } } }

服务

 import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; interface EmailValidationResponse { address: string, account: string, domain: string, status: string, connected: string, disposable: boolean, role_address: boolean, error_code?: string, error?: string, duration: number } @Injectable() export class EmailValidationService { public emailValidationUrl = 'https://briteverifyendpoint.com'; constructor( private http: HttpClient ) { } validateEmail(value) { let params = new HttpParams(); params = params.append('address', value); return this.http.get<EmailValidationResponse>(this.emailValidationUrl, { params: params }); } }

模板(只是形式)

 <form class="email-form" [formGroup]="emailForm" (ngSubmit)="sendEmail()"> <div class="row"> <div class="col-md-12 col-sm-12 col-xs-12"> <fieldset class="form-group required" [ngClass]="{ 'has-error': email.invalid && formSubmitted }"> <div>{{ email.status }}</div> <label class="control-label" for="email">Email</label> <input class="form-control input-lg" name="email" id="email" formControlName="email"> <ng-container *ngIf="email.invalid && formSubmitted"> <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>&nbsp;Please enter valid email address. </ng-container> </fieldset> <button type="submit" class="btn btn-primary btn-lg btn-block">Send</button> </div> </div> </form>

有一个问题

也就是说,你的 observable 永远不会完成......

这是因为 observable 永远不会完成,所以 Angular 不知道何时更改表单状态。 所以记住你的 observable 必须完成。

您可以通过多种方式完成此操作,例如,您可以调用first()方法,或者如果您正在创建自己的 observable,您可以在观察者上调用 complete 方法。

所以你可以使用first()

RXJS 6 更新:

briteVerifyValidator(service: Service) {
  return (control: AbstractControl) => {
    if (!control.valueChanges) {
      return of(null);
    } else {
      return control.valueChanges.pipe(
        debounceTime(1000),
        distinctUntilChanged(),
        switchMap(value => service.getData(value)),
        map(data => {
          return data.status === 'invalid' ? { invalid: true } : null;
        })
      ).pipe(first())
    }
  }
}

一个稍微修改的验证器,即总是返回错误: STACKBLITZ


旧:

.map(data => {
   return data.status === 'invalid' ? { invalid: true } : null;
})
.first();

一个稍微修改的验证器,即总是返回错误: STACKBLITZ

所以我所做的是在没有使用用户名时抛出 404 并使用订阅错误路径来解析为空,当我得到响应时,我解决了一个错误。 另一种方法是返回一个数据属性,要么填充用户名的宽度,要么通过响应对象为空,并使用 404 的 insead

例如。

在这个例子中,我绑定 (this) 以便能够在验证器函数中使用我的服务

我的组件类 ngOnInit() 的摘录

//signup.component.ts

constructor(
 private authService: AuthServic //this will be included with bind(this)
) {

ngOnInit() {

 this.user = new FormGroup(
   {
    email: new FormControl("", Validators.required),
    username: new FormControl(
      "",
      Validators.required,
      CustomUserValidators.usernameUniqueValidator.bind(this) //the whole class
    ),
    password: new FormControl("", Validators.required),
   },
   { updateOn: "blur" });
}

来自我的验证器类的摘录

//user.validator.ts
...

static async usernameUniqueValidator(
   control: FormControl
): Promise<ValidationErrors | null> {

 let controlBind = this as any;
 let authService = controlBind.authService as AuthService;  
 //I just added types to be able to get my functions as I type 

 return new Promise(resolve => {
  if (control.value == "") {
    resolve(null);
  } else {
    authService.checkUsername(control.value).subscribe(
      () => {
        resolve({
          usernameExists: {
            valid: false
          }
        });
      },
      () => {
        resolve(null);
      }
    );
  }
});

...

我的做法略有不同,并面临着同样的问题。

这是我的代码和修复,以防万一有人需要它:

  forbiddenNames(control: FormControl): Promise<any> | Observable<any> {
    const promise = new Promise<any>((resolve, reject) => {
      setTimeout(() => {
        if (control.value.toUpperCase() === 'TEST') {
          resolve({'nameIsForbidden': true});
        } else {

          return null;//HERE YOU SHOULD RETURN resolve(null) instead of just null
        }
      }, 1);
    });
    return promise;
  }

我尝试使用.first() @AT82 描述的技术,但我没有发现它解决了问题。

我最终发现表单状态正在发生变化,但因为我使用的是onPush ,状态变化没有触发变化检测,所以页面中没有任何更新。

我最终采用的解决方案是:

export class EmailFormComponent implements OnInit {
    ...
    constructor(
        ...
        private changeDetector: ChangeDetectorRef,
    ) {

      ...

      // Subscribe to status changes on the form
      // and use the statusChange to trigger changeDetection
      this.myForm.statusChanges.pipe(
        distinctUntilChanged()
      ).subscribe(() => this.changeDetector.markForCheck())
    }

}

 import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { EmailValidationService } from '../services/email-validation.service'; import { CustomValidators } from '../utilities/custom-validators/custom-validators'; @Component({ templateUrl: './email-form.component.html', styleUrls: ['./email-form.component.sass'] }) export class EmailFormComponent implements OnInit { public emailForm: FormGroup; public formSubmitted: Boolean; public emailSent: Boolean; constructor( private router: Router, private builder: FormBuilder, private service: EmailValidationService ) { } ngOnInit() { this.formSubmitted = false; this.emailForm = this.builder.group({ email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ] }); } get email() { return this.emailForm.get('email'); } // rest of logic }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM