[英]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 }
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> 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.