我正在构建一个 Angular 4 应用程序,它需要对多个组件中的表单字段进行 BriteVerify 电子邮件验证。我正在尝试将此验证实现为自定义异步验证器,我可以将其与反应式表单一起使用。目前,我可以得到 API 响应,但控件状态停留在待处理状态。我没有收到任何错误,所以我有点困惑。请告诉我我做错了什么。这是我的代码。
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>
有一个问题!
也就是说,你的可观察对象永远不会完成......
发生这种情况是因为可观察对象永远不会完成,因此 Angular 不知道何时更改表单状态。所以请记住您的可观察量必须完成。
您可以通过多种方式完成此操作,例如,您可以调用
方法,或者如果您正在创建自己的可观察对象,则可以在观察者上调用完整方法。first()
所以你可以使用
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 并使用订阅错误路径来解析 null,当我确实得到响应时,我解决了一个错误。另一种方法是返回一个数据属性,要么填充用户名的宽度,要么为空 通过响应对象并使用它来代替 404
例如。
在此示例中,我绑定(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
}
就我而言,我在保存之前更新了表单的值和有效性:
public saveValidated() {
const fullFormGroup = this.clientProfileFormProviderService.getFullForm();
fullFormGroup.markAllAsTouched();
updateValueAndValidityRecursively(fullFormGroup);
if (fullFormGroup.valid) {
this.saveForm(fullFormGroup);
} else {
this.handleInvalidForm(fullFormGroup);
}
}
我的异步验证器看起来像这样:
private checkNameUniqueness(currentName: string): AsyncValidatorFn {
return (control: FormControl) => {
if (String(control.value) == currentName) {
return of(null);
}
return this.clientProfileApiService.existsClientProfileName(control.value).pipe(
map(exists => !exists ? null : {nonUnique: true})
).pipe(first());
}
}
那么幕后发生了什么:
改变
return this.clientProfileApiService.existsClientProfileName(control.value).pipe(
map(exists => !exists ? null : {nonUnique: true})
).pipe(first());
进入
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
(...)
不会改变任何东西,因为我们每次验证时都会触发该函数。因此,每次我们订阅 valueChanges 时,distinctUntilChanged() 都不起作用,因为它是一个新流!
就我而言,我们有两种可能的解决方案:
private checkNameUniqueness(currentName: string): AsyncValidatorFn {
return (control: FormControl) => {
if (String(control.value) == currentName) {
return of(null);
}
if (CACHED_NAME_VALUE == String(control.value)) {
return of(null);
}
return this.clientProfileApiService.existsClientProfileName(control.value).pipe(
switchMap(value => this.clientProfileApiService.existsClientProfileName(control.value)),
map(exists => !exists ? null : {nonUnique: true})
).pipe(first());
}
}
public saveValidated() {
const fullFormGroup = this.clientProfileFormProviderService.getFullForm();
fullFormGroup.markAllAsTouched();
updateValueAndValidityRecursively(fullFormGroup);
fullFormGroup.statusChanges.pipe(
startWith(fullFormGroup.status),
filter(status => status != 'PENDING'),
take(1)
).subscribe(val => {
if (fullFormGroup.valid) {
this.saveForm(fullFormGroup);
} else {
this.handleInvalidForm(fullFormGroup);
}
});
}
我选择了选项 2,因为它更通用 - 即使内部没有异步验证器,它也会按预期工作;)