javascript Angular 4:反应式表单控件使用自定义异步验证器卡在挂起状态
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/48655324/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Angular 4: reactive form control is stuck in pending state with a custom async validator
提问by Andre Kuzmicheff
I am building an Angular 4 app that requires the BriteVerify email validation on form fields in several components. I am trying to implement this validation as a custom async validator that I can use with reactive forms. Currently, I can get the API response, but the control status is stuck in pending state. I get no errors so I am a bit confused. Please tell me what I am doing wrong. Here is my code.
我正在构建一个 Angular 4 应用程序,它需要对多个组件中的表单字段进行 BriteVerify 电子邮件验证。我正在尝试将此验证实现为可以与响应式表单一起使用的自定义异步验证器。目前,我可以得到 API 响应,但控制状态卡在挂起状态。我没有收到任何错误,所以我有点困惑。请告诉我我做错了什么。这是我的代码。
Component
零件
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
}
Validator 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;
});
}
}
}
}
Service
服务
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
});
}
}
Template (just form)
模板(只是形式)
<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>
回答by AJT82
There's a gotcha!
有一个问题!
That is, your observable never completes...
也就是说,你的 observable 永远不会完成......
This is happening because the observable never completes, so Angular does not know when to change the form status. So remember your observable must to complete.
You can accomplish this in many ways, for example, you can call the
first()method, or if you are creating your own observable, you can call the complete method on the observer.
发生这种情况是因为 observable 永远不会完成,所以 Angular 不知道何时更改表单状态。所以记住你的 observable 必须完成。
您可以通过多种方式完成此操作,例如,您可以调用该
first()方法,或者如果您正在创建自己的 observable,您可以在观察者上调用完整的方法。
So you can use first()
所以你可以使用 first()
.map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
})
.first();
A slightly modified validator, i.e always returns error: STACKBLITZ
一个稍微修改的验证器,即总是返回错误:STACKBLITZ
回答by alexlz
I've been doing it slightly differently and faced the same issue.
我的做法略有不同,并面临着同样的问题。
Here is my code and the fix in case if someone would need it:
这是我的代码和修复,以防万一有人需要它:
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;
}
回答by Miguel Angel Romero Hernandez
So what I did was to throw a 404 when the username was not taken and use the subscribe error path to resolve for null, and when I did get a response I resolved with an error. Another way would be to return a data property either filled width the username or empty through the response object and use that insead of the 404
所以我所做的是在没有使用用户名时抛出 404 并使用订阅错误路径来解析为空,当我得到响应时,我用错误解决了。另一种方法是返回一个数据属性,要么填充用户名的宽度,要么通过响应对象为空,并使用 404 的 insead
Ex.
前任。
In this example I bind (this) to be able to use my service inside the validator function
在这个例子中,我绑定 (this) 以便能够在验证器函数中使用我的服务
An extract of my component class ngOnInit()
我的组件类 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" });
}
An extract from my validator class
我的验证器类的摘录
//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);
}
);
}
});
...

