非同期バリデータは次のように定義される:
constructor(controls: {
[key: string]: AbstractControl;
}, validator?: ValidatorFn | null, asyncValidator?: AsyncValidatorFn | null);
したがって非同期検証関数がAbstractControl
インスタンスを受信する、ここで:FormBuilder.group()
はFormGroup
コンストラクタを呼び出す実際にあるからである
export interface AsyncValidatorFn {
(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;
}
この場合、バリデーターはFormGroup
レベルに配置されているため、FormGroup
インスタンスです。バリデータは、Promise
またはObservable
のValidationErrors
を返す必要があります。検証エラーがない場合はnullを返します。
ValidationErrors
は、文字列のキーと値のマップ(好きなもの)として定義されています。実際には、キーは検証エラーの種類を定義する文字列です(例: "required")。
export declare type ValidationErrors = {
[key: string]: any;
};
AbstractControl.setErrors()?
- あなたの例では、あなたが何かを返しますが、実際には直接制御誤差を変更しない関数を定義しています。 setErrors
を呼び出すと、検証が手動で呼び出され、エラーが手動でのみ設定される場合にのみ機能します。代わりに、あなたの例では、アプローチは混在しており、FormControl
には自動的に実行される検証機能が付属しています。また、自動的に実行されるFormGroup
非同期検証機能もエラーとその妥当性を手動で設定しようとします。これは動作しません。あなたは2つのアプローチのいずれかで行く必要がある
:
- はエラーとも妥当性を設定するため、自動的に実行された検証機能を取り付けます。検証関数が添付されたコントロールで手動で何も設定しようとしないでください。
- 影響を受けた
AbstractControl
インスタンスに検証関数を付加することなく、エラーと有効性を手動で設定します。
すべてをきれいにしたい場合は、個別の検証機能を実装することができます。 FormControl
検証では、1つのコントロールのみが処理されます。 FormGroup
検証では、フォームグループ全体の複数の側面が処理されます。
実際にフォーム全体を検証し、それぞれの適切なコントロールバリデーターにそれぞれのエラーを委譲する検証サービスを使用する場合は、Solution 2を参照してください。これはちょっと難しいです。
バリデーションサービスを使用するFormGroup
レベルのバリデータを持っているのであれば、ソリューション1を使用してください。
ソリューション1 - FormGroupレベルでのエラーを作成
我々は姓と名が、最初の名前は、姓と異なるする必要が入力したいとします。この計算に1秒かかると仮定します。
テンプレート
<form [formGroup]="personForm">
<div>
<input type="text" name="firstName" formControlName="firstName" placeholder="First Name" />
</div>
<div>
<input type="text" name="lastName" formControlName="lastName" placeholder="Last Name" />
</div>
<p style="color: red" *ngIf="personForm.errors?.sameValue">First name and last name should not be the same.</p>
<button type="submit">Submit</button>
</form>
コンポーネントが
以下validateBusiness
検証機能がPromise
戻ります:
import { Component, OnInit } from '@angular/core';
import {AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators} from "@angular/forms";
import {Observable} from "rxjs/Observable";
import "rxjs/add/operator/delay";
import "rxjs/add/operator/map";
import "rxjs/add/observable/from";
@Component({
selector: 'app-async-validation',
templateUrl: './async-validation.component.html',
styleUrls: ['./async-validation.component.css']
})
export class AsyncValidationComponent implements OnInit {
personForm: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
ngOnInit() {
this.personForm = this._formBuilder.group({
firstName: [ '', Validators.required ],
lastName: [ '', Validators.required ],
}, {
asyncValidator: this.validateBusiness.bind(this)
});
}
validateBusiness(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (control.value.firstName !== control.value.lastName) {
resolve(null);
}
else {
resolve({sameValue: 'ERROR...'});
}
},
1000);
});
}
}
あるいは、検証機能はを返すことができます:
validateBusiness(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
return Observable
.from([control.value.firstName !== control.value.lastName])
.map(valid => valid ? null : {sameValue: 'ERROR...'})
.delay(1000);
}
解決策2 - 複数のコントロールの検証エラーを編成
別のオプションは、フォームの変更とは、後により使用することが可能な観察に結果を渡すときに手動で検証することですFormGroup
およびFormControl
非同期バリデータ。
私はPOC hereを作成しました。
IValidationResponse
検証サービスからの応答は、フォームデータを検証するために使用されます。
import {IValidationErrorDescription} from "./IValidationErrorDescription";
export interface IValidationResponse {
validations: IValidationErrorDescription[];
}
IValidationErrorDescription
検証エラーの説明を応答。フォームデータを検証する事業を実装
export interface IValidationErrorDescription {
display: string;
code: string;
fields: string[];
}
BusinessValidationService
検証サービス。FormGroup
とFormControl
のための非同期バリデータを構築し、検証コールバック(:BusinessValidationService
など)に検証を委任するために、フォームデータの変更をサブスクライブするために使用される
import { Injectable } from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/map';
import {IValidationResponse} from "../model/IValidationResponse";
@Injectable()
export class BusinessValidationService {
public validateForm(value: any): Observable<IValidationResponse> {
return Observable
.from([value.firstName !== value.lastName])
.map(valid => valid ?
{validations: []}
:
{
validations: [
{
code: 'sameValue',
display: 'First name and last name are the same',
fields: ['firstName', 'lastName']
}
]
}
)
.delay(500);
}
}
FormValidationService
検証サービス。
それは以下を提供する:
validateFormOnChange()
- フォームは、それが検証コールバックvalidateFormCallback
を呼び出し、それがcontrol.validateFormGroup()
を用いFormGroup
とFormControl
Sの検証をトリガするときに変化します。
createGroupAsyncValidator()
は - FormGroup
createControlAsyncValidator()
ための非同期バリデータを作成 - FormControl
ための非同期バリをコード作成:
import { Injectable } from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/debounceTime';
import {AbstractControl, AsyncValidatorFn, FormGroup} from '@angular/forms';
import {ReplaySubject} from 'rxjs/ReplaySubject';
import {IValidationResponse} from "../model/IValidationResponse";
@Injectable()
export class FormValidationService {
private _subject$ = new ReplaySubject<IValidationResponse>(1);
private _validationResponse$ = this._subject$.debounceTime(100).share();
private _oldValue = null;
constructor() {
this._subject$.subscribe();
}
public get onValidate(): Observable<IValidationResponse> {
return this._subject$.map(response => response);
}
public validateFormOnChange(group: FormGroup, validateFormCallback: (value: any) => Observable<IValidationResponse>) {
group.valueChanges.subscribe(value => {
const isChanged = this.isChanged(value, this._oldValue);
this._oldValue = value;
if (!isChanged) {
return;
}
this._subject$.next({validations: []});
this.validateFormGroup(group);
validateFormCallback(value).subscribe(validationRes => {
this._subject$.next(validationRes);
this.validateFormGroup(group);
});
});
}
private isChanged(newValue, oldValue): boolean {
if (!newValue) {
return true;
}
return !!Object.keys(newValue).find(key => !oldValue || newValue[key] !== oldValue[key]);
}
private validateFormGroup(group: FormGroup) {
group.updateValueAndValidity({ emitEvent: true, onlySelf: false });
Object.keys(group.controls).forEach(controlName => {
group.controls[controlName].updateValueAndValidity({ emitEvent: true, onlySelf: false });
});
}
public createControlAsyncValidator(fieldName: string): AsyncValidatorFn {
return (control: AbstractControl) => {
return this._validationResponse$
.switchMap(validationRes => {
const errors = validationRes.validations
.filter(validation => validation.fields.indexOf(fieldName) >= 0)
.reduce((errorMap, validation) => {
errorMap[validation.code] = validation.display;
return errorMap;
}, {});
return Observable.from([errors]);
})
.first();
};
}
public createGroupAsyncValidator(): AsyncValidatorFn {
return (control: AbstractControl) => {
return this._validationResponse$
.switchMap(validationRes => {
const errors = validationRes.validations
.reduce((errorMap, validation) => {
errorMap[validation.code] = validation.display;
return errorMap;
}, {});
return Observable.from([errors]);
})
.first();
};
}
}
AsyncFormValidateComponentテンプレートを
を定義しますfirstName
および FormControl
は、personForm
FormGroup
の内部にあります。この例の条件は、firstName
とlastName
が異なるはずです。
<form [formGroup]="personForm">
<div>
<label for="firstName">First name:</label>
<input type="text"
id="firstName"
name="firstName"
formControlName="firstName"
placeholder="First Name" />
<span *ngIf="personForm.controls['firstName'].errors?.sameValue">Same as last name</span>
</div>
<div>
<label for="lastName">Last name:</label>
<input type="text"
id="lastName"
name="lastName"
formControlName="lastName"
placeholder="Last Name" />
<span *ngIf="personForm.controls['lastName'].errors?.sameValue">Same as first name</span>
</div>
<p style="color: red" *ngIf="personForm.errors?.sameValue">First name and last name should not be the same.</p>
<button type="submit">Submit</button>
</form>
AsyncValidateFormComponent
FrmValidationService
を用いて検証を実施するために例として使用される成分。このコンポーネントはのためにこのサービスの独自のインスタンスを持っています。 Angular階層インジェクタ機能により、1つのインジェクタがこのコンポーネントに関連付けられ、AsyncValidateFormComponent
の各インスタンスに対してこのサービスの1つのインスタンスが作成されます。したがって、このサービス内の検証状態をコンポーネントごとのインスタンスごとに追跡することができます。
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/from';
import {FormValidationService} from "../services/form-validation.service";
import {BusinessValidationService} from "../services/business-validation.service";
@Component({
selector: 'app-async-validate-form',
templateUrl: './async-validate-form.component.html',
styleUrls: ['./async-validate-form.component.css'],
providers: [FormValidationService]
})
export class AsyncValidateFormComponent implements OnInit {
personForm: FormGroup;
constructor(private _formBuilder: FormBuilder,
private _formValidationService: FormValidationService,
private _businessValidationService: BusinessValidationService) {
}
ngOnInit() {
this.personForm = this._formBuilder.group({
firstName: ['', Validators.required, this._formValidationService.createControlAsyncValidator('firstName')],
lastName: ['', Validators.required, this._formValidationService.createControlAsyncValidator('lastName')],
}, {
asyncValidator: this._formValidationService.createGroupAsyncValidator()
});
this._formValidationService.validateFormOnChange(this.personForm, value => this._businessValidationService.validateForm(value));
}
}
AppModule
それはFormBuilder
、FormGroup
とFormControl
を使用するためにReactiveFormsModule
を使用しています。 BusinessValidationService
も提供しています。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { AsyncValidateFormComponent } from './async-validate-form/async-validate-form.component';
import {BusinessValidationService} from "./services/business-validation.service";
@NgModule({
declarations: [
AppComponent,
AsyncValidateFormComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpModule
],
providers: [
BusinessValidationService
],
bootstrap: [AppComponent]
})
export class AppModule { }
あなたの解決策を読むのに私はしばらく時間をかかりました:)しかし、それは私のエラーを見つけて修正するのを助けました。 Thx – ylerjen
@andreim Miam84私は最初の解決策に従ったが、それは私と一緒には機能しなかった、あなたはプランナーに解決策を入れてください – Hany