2017-05-23 7 views
1

...angular2 formBuilderグループ非同期検証

私のコンポーネントが持つフォームを作成します。

this.personForm = this._frmBldr.group({ 
    lastname: [ '', Validators.compose([Validators.required, Validators.minLength(2) ]) ], 
    firstname: [ '', Validators.compose([Validators.required, Validators.minLength(2) ]) ], 
    birthdate: [ '', Validators.compose([ Validators.required, DateValidators.checkIsNotInTheFuture ]) ], 
    driverLicenceDate: [ '', Validators.compose([ Validators.required, DateValidators.checkIsNotInTheFuture ]), this.asyncValidationLicenceDate.bind(this) ], 
}, { 
    asyncValidator: this.validateBusiness.bind(this), 
    validator: this.validateDriverLicenseOlderThanBirthdate, 
}); 

私の検証方法

validateBusiness(group: FormGroup) { 
    console.log('validateBusiness') 
    return this._frmService 
    .validateForm(group.value) 
    .map((validationResponse: IValidationResponse) => { 
     if (validationResponse) { 
     validationResponse.validations.forEach((validationError: IValidationErrorDescription) => { 
         let errorMsg = validationError.display; 
         let errorCode = validationError.code; 
         validationError.fields.forEach((fieldName: string) => { 
          console.log(fieldName); 
          let control = this.personForm.controls[fieldName]; 
          let existingErrors = control.errors || {}; 
          existingErrors[errorCode] = errorMsg; 
          control.setErrors(existingErrors); 
         }); 
        }); 
       } 
      }); 
    } 

決して呼び出されないvalidateBusinessメソッド(のextra.asyncValidatorパラメータ内)を除き、すべての検証はsuccessfulyと呼ばれます。私は間違っている?

のTx

答えて

4

TL; DR:あなたのユースケースを分析することで、あなたは問題

問題は非同期バリデータがどのように定義されるかであるに解決策2

が必要になる場合があります使用されます。

非同期バリデータは次のように定義される:

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またはObservableValidationErrorsを返す必要があります。検証エラーがない場合はnullを返します。

ValidationErrorsは、文字列のキーと値のマップ(好きなもの)として定義されています。実際には、キーは検証エラーの種類を定義する文字列です(例: "required")。

export declare type ValidationErrors = { 
    [key: string]: any; 
}; 

AbstractControl.setErrors()? - あなたの例では、あなたが何かを返しますが、実際には直接制御誤差を変更しない関数を定義しています。 setErrorsを呼び出すと、検証が手動で呼び出され、エラーが手動でのみ設定される場合にのみ機能します。代わりに、あなたの例では、アプローチは混在しており、FormControlには自動的に実行される検証機能が付属しています。また、自動的に実行されるFormGroup非同期検証機能もエラーとその妥当性を手動で設定しようとします。これは動作しません。あなたは2つのアプローチのいずれかで行く必要がある

  1. はエラーとも妥当性を設定するため、自動的に実行された検証機能を取り付けます。検証関数が添付されたコントロールで手動で何も設定しようとしないでください。
  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

検証サービス。FormGroupFormControlのための非同期バリデータを構築し、検証コールバック(: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()を用いFormGroupFormControl 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は、personFormFormGroupの内部にあります。この例の条件は、firstNamelastNameが異なるはずです。

<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

それはFormBuilderFormGroupFormControlを使用するために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 { } 
+0

あなたの解決策を読むのに私はしばらく時間をかかりました:)しかし、それは私のエラーを見つけて修正するのを助けました。 Thx – ylerjen

+0

@andreim Miam84私は最初の解決策に従ったが、それは私と一緒には機能しなかった、あなたはプランナーに解決策を入れてください – Hany

関連する問題