web-dev-qa-db-ja.com

コンポーネントを使用したフォームエラーの処理Angular-TypeScript

現在、いくつかのフィールド(10個以上のフィールド)の Angular / TypeScript のフォームに取り組んでおり、HTMLでコードを複製せずにエラーをより適切に管理したかったページ。

フォームの例を次に示します。

<form [formGroup]="myForm">
     <label>Name</label>
     <input type="text" formControlName="name">
     <p class="error_message" *ngIf="myForm.get('name').invalid && (myForm.submitted || myForm.get('name').dirty)">Please provide name</p>
     <label>Lastname</label>
     <input type="text" formControlName="lastname">
     <p class="error_message" *ngIf="myForm.get('lastname').invalid && (myForm.submitted || myForm.get('lastname').dirty)">Please provide email</p>
     <label>Email</label>
     <input type="text" formControlName="email">
     <p class="error_message" *ngIf="myForm.get('email').hasError('required') && (myForm.submitted || myForm.get('email').dirty)">Please provide email</p>
     <p class="error_message" *ngIf="myForm.get('email').hasError('email') && (myForm.submitted || myForm.get('email').dirty)">Please provide valid email</p>
</form>

私の場合、フォームには2種類の検証があります。

  • HTML検証:必須、maxSizeなど.
  • バック検証:たとえば、無効なアカウント、ロードされたファイルのサイズなど。

私はディレクティブを使用しようとしました ここで述べたように

<form [formGroup]="myForm">
     <label>Name</label>
     <input type="text" formControlName="name">
     <div invalidmessage="name">
        <p *invalidType="'required'">Please provide name</p>
     </div>
     <label>Lastname</label>
     <input type="text" formControlName="lastname">
     <div invalidmessage="lastname">
        <p *invalidType="'required'">Please provide lastname</p>
     </div>
     <label>Email</label>
     <input type="text" formControlName="email">
     <div invalidmessage="email">
        <p *invalidType="'required'">Please provide email</p>
        <p *invalidType="'email'">Please provide valid email</p>
     </div>
</form>

ただし、このソリューションを使用しても、コードは常に複製され、両方のタイプの検証を処理することはできません。

別のアプローチがありますか?この場合、使用コンポーネントは適切ですか?はいの場合、どのようにそれを行うことができます。

投資をありがとう。

24
Lyes CHIOUKH

検証エラーをコンポーネントに移動して、formControl.errorsを入力プロパティとして渡すことができます。これにより、すべての検証メッセージを再利用できます。 StackBlitz の例を次に示します。コードはAngularマテリアルを使用していますが、使用していなくても便利です。

validation-errors.component.ts

import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, ValidationErrors } from '@angular/forms';

@Component({
  selector: 'validation-errors',
  templateUrl: './validation-errors.component.html',
  styleUrls: ['./validation-errors.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ValidationErrorsComponent implements OnInit {
  @Input() errors: ValidationErrors;

  constructor() {}

  ngOnInit() {}

}

validation-errors.component.html

<ng-container *ngIf="errors && errors['required']"> Required</ng-container>
<ng-container *ngIf="errors && errors['notUnique']">Already exists</ng-container>
<ng-container *ngIf="errors && errors['email']">Please enter a valid email</ng-container>

バック検証メッセージの場合、フォームコントロールでエラーを手動で設定します。

const nameControl = this.userForm.get('name');
nameControl.setErrors({
  "notUnique": true
});

フォームで検証コンポーネントを使用するには:

   <form [formGroup]="userForm" (ngSubmit)="submit()">
      <mat-form-field>
        <input matInput placeholder="name" formControlName="name" required>
        <mat-error *ngIf="userForm.get('name').status === 'INVALID'">
          <validation-errors [errors]="userForm.get('name').errors"></validation-errors>      
        </mat-error>
      </mat-form-field>
      <mat-form-field>
        <input matInput placeholder="email" formControlName="email" required>
        <mat-error *ngIf="userForm.get('email').status === 'INVALID'">
          <validation-errors [errors]="userForm.get('email').errors"></validation-errors>
        </mat-error>
      </mat-form-field>
      <button mat-raised-button class="mat-raised-button" color="accent">SUBMIT</button>
    </form>
23
JayChase

デモ

NgFormを挿入し、カスタム検証コンポーネント内の@ContentChildを介してFormControlNameディレクティブにアクセスして、再利用を実現できます。

@Component({
  selector: '[validator]',
  template: `
    <ng-content></ng-content>
    <div *ngIf="formControl.invalid">
        <div *ngIf="formControl.errors.required && (form.submitted || formControl.dirty)">
             Please provide {{ formControl.name }}
        </div>
        <div *ngIf="formControl.errors.email && (form.submitted || formControl.dirty)">
             Please provide a valid email
        </div>
        <div *ngIf="formControl.errors.notstring && (form.submitted || formControl.dirty)">
             Invalid name
        </div>

    </div>
`})

export class ValidatorComponent implements OnInit {
   @ContentChild(FormControlName) formControl;
   constructor(private form: NgForm) { 

   }

   ngOnInit() { }

}

これを使用するには、すべてのフォームコントロール(formControlNameを持つ)をHTML要素でラップし、バリデーター属性を追加します。

<form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
<div [formGroup]="myForm">
     <label>Name</label>
     <div validator>
         <input type="text" formControlName="name">
     </div>
     <label>Lastname</label>
     <div validator>
         <input type="text" formControlName="lastname">
     </div>
     <label>Email</label>
     <div validator>
         <input type="text" formControlName="email">
     </div>
</div>
<button type="submit">Submit</button>
</form>

これは、同期および非同期バリデーターで機能します。

4
pixelbits

同じ要件があり、同じコードを2回書き直したい人はいません。

これは、カスタムフォームコントロールを作成することで実行できます。アイデアは、カスタムフォームコントロールを作成し、カスタムformControlオブジェクトを生成し、FormControlオブジェクトに提供されたデータ型に基づいて適切なバリデーターを注入する共通サービスを使用することです。

データ型はどこから来たのですか?

次のようなタイプを含むアセットまたは任意の場所にファイルがあります。

[{
  "nameType" : {
   maxLength : 5 , 
   minLength : 1 , 
   pattern  :  xxxxxx,
   etc
   etc

   }
}
]

これをValidatorServiceで読み取り、バリデーターを作成してカスタムフォームコントロールに戻ることができる適切なDataTypeを選択できます。

例えば ​​、

<ui-text name="name" datatype="nameType" [(ngModel)]="data.name"></ui-text>

これは私がこれを達成するためにしたことの高レベルでの簡単な説明です。これに関する追加情報が必要な場合は、コメントしてください。すぐにコードベースを提供することはできませんが、明日には答えが更新される可能性があります。

パーツを表示するエラーの更新

あなたはそれのために2つのことをすることができ、フォームコントロールのバリデータをコントロール内のdivにバインドし、*ngIf="formControl.hasError('required) "`などで切り替えることができます。

メッセージボードなどの別の一般的な場所にメッセージ/エラーを表示するには、ルーティング中に削除されないParentComponentのどこかにメッセージボードマークアップを配置し(要件に基づいて議論可能)、そのコンポーネントにMessageEmitイベントをリッスンさせることをお勧めしますformControlのErrorStateMatcherは、必要に応じて(要件に基づいて)起動します。

これは私たちが使用した設計であり、非常にうまく機能しました。カスタマイズを開始すると、これらのformControlで多くのことができます。

2
Aakash Uniyal

カスタムコンポーネントValidationMessagesComponentを作成できます。

テンプレート :

<p class="error_message" *ngIf="form.get(controlName).hasError('required') && (form.submitted || form.get(controlName).dirty)">Please provide {{controlName}}</p>
<p class="error_message" *ngIf="form.get(controlName).hasError('email') && (form.submitted || form.get(controlName).dirty)">Please provide valid {{controlName}}</p>
...other errors

そして入力で:

@Input() controlName;
@Input() form;

次に、このように使用します:

<validation-messages [form]="myForm" controlName="email"></validation-messages>
1
ibenjelloun

以下は、動的フォームを生成するためにライブラリで使用したコードの一部です。

これはFormError.tsであり、必要に応じてエラーメッセージとカスタムメッセージを取得するために使用されます。

import { AbstractControl } from "@angular/forms";

type ErrorFunction = (errorName: string, error: object) => string;
export type ErrorGetter =
    string | { [key2: string]: string } | ErrorFunction;

export class FormError {
    constructor(private errorGetter?: ErrorGetter) { }
    hasError(abstractControl: AbstractControl) {
        return abstractControl.errors && (abstractControl.dirty || abstractControl.touched);
    }
    getErrorMsgs(abstractControl: AbstractControl): string[] {
        if (!this.hasError(abstractControl))
            return null;
        let errors = abstractControl.errors;
        return Object.keys(errors).map(anyError => this.getErrorValue(anyError, errors[anyError]));
    }
    getErrorValue(errorName: string, error: object): string {
        let errorGetter = this.errorGetter;
        if (!errorGetter)
            return predictError(errorName, error);
        if (isString(errorGetter))
            return errorGetter;
        else if (isErrorFunction(errorGetter)) {
            let errorString = errorGetter(errorName, error);
            return this.predictedErrorIfEmpty(errorString, errorName, error)
        }
        else {
            let errorString = this.errorGetter[errorName];
            return this.predictedErrorIfEmpty(errorString, errorName, error)
        }
    }
    predictedErrorIfEmpty(errorString: string, errorName: string, error: object) {
        if (errorString == null || errorString == undefined)
            return predictError(errorName, error);
        return errorString;
    }


}
function predictError(errorName: string, error: object): string {
    if (errorName === 'required')
        return 'Cannot be blank';
    if (errorName === 'min')
        return `Should not be less than ${error['min']}`;
    if (errorName === 'max')
        return `Should not be more than ${error['max']}`;
    if (errorName === 'minlength')
        return `Alteast ${error['requiredLength']} characters`;
    if (errorName === 'maxlength')
        return `Atmost ${error['requiredLength']} characters`;
    // console.warn(`Error for ${errorName} not found. Error object = ${error}`);
    return 'Error';
}
export function isString(s: any): s is string {
    return typeof s === 'string' || s instanceof String;
}
export function isErrorFunction(f: any): f is ErrorFunction {
    return typeof f === "function";
}

カスタムメッセージ

 class FormError {
    constructor(private errorGetter?: ErrorGetter) { }
    }

ErrorGetterは次のようになります

type ErrorFunction = (errorName: string, error: object) => string;
type ErrorGetter =
    string | { [key2: string]: string } | ErrorFunction;
  1. エラーに対して一定のエラーが必要な場合は、次のようにする必要があります

    new FormError('Password is not right')

  2. 特定のエラーに対して一定のエラーが必要な場合は、次のようにする必要があります

    new FormError({required:'Address is necessary.'})

    他のエラーについては、エラーを予測します。

  3. 特定のエラーに関数を使用したい場合は、次のようにする必要があります

    new FormError((errorName,errorObject)=>{ if(errorName=='a') return '2';})

    他のエラーについては、エラーを予測します。

  4. 必要に応じてpredictError関数を変更します。

FormErrorコンポーネント

form-error.html

<ng-container *ngIf="formError.hasError(control)">
  <div class='form-error-message' *ngFor='let error of  formError.getErrorMsgs(control)'>{{error}}</div>
</ng-container>

form-error.scss

form-error {
    .form-error-message {
        color: red;
        font-size: .75em;
        padding-left: 16px;
    }
}

form-error.ts

@Component({
  selector: 'form-error',
  templateUrl: 'form-error.html'
})
export class FormErrorComponent {
  @Input() formError: FromError;
  @Input() control: AbstractControl;
}

使用法

<form-error [control]='thatControl' ></form-error>

明らかにFormErrorは最適な設計ではありません。好きなように変更します。

1
amit77309

最良の方法は、各タイプの入力にカスタムControlValueAccessorsを実装し、<label><input>、およびエラーメッセージを表示するいくつかのタグを組み合わせることです(私のプロジェクトでは、単にtitle属性を使用しますこの目的のために)単一のコンポーネントで。

すべての値アクセサーは、同じインターフェイスを実装するか、基本抽象クラスを拡張して、エラーメッセージやバリデーターディレクティブから呼び出したい他のメソッドを設定およびクリアするメソッドを提供する必要があります。

また、検証タイプごとにカスタム検証ディレクティブを実装する必要があります(requiredおよびmaxlengthでも再実装する必要がありました)、検証はエラーオブジェクトを均一な方法で返す必要があります。つまり、電子メール検証{email: "Invalid email address"}。バリデーターディレクティブは、インジェクション-@Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[](通常は1つの要素を持つ配列、AbstractFormComponentはアクセサーの基本クラスです)を介してコントロール値アクセサーへの参照を取得できます。この参照を使用して、アクセサーエラーメッセージを設定またはクリアします。

検証ディレクティブの追加の2つのタイプを実装することもできます。syncとasyncは、@Inputを介して検証関数を受け取ることができます。つまり、[async]="loginValidatorFn"で、loginValidatorFnはコンポーネントクラスで定義され、Observable<ValidationErrors>

これはアプリケーションの実際のコードです。

<div class="input" [caption]="'SSN: '" name="ssn" type="text" [(ngModel)]="item.ssn" [async]="memberSsnValidatorFn" required></div>
1
kemsky

Html検証では、基本的に入力のラッパーとなる custom formcontrol を記述します。また、エラーメッセージを返すカスタムバリデータを作成します(ビルトインバリデータは、私が信じているオブジェクトを返します)。カスタムフォームコントロール内で、次のようなことができます。

<div *ngIf="this.formControl.errors">
    <p>this.formControl.errors?.message</p>
</div>

バックエンドバリデーターについては、 async validator と書くことができます。

1
Robin Dijkhof

テンプレートコードを明確にし、検証メッセージの重複コードを回避するには、それらをより再利用可能に変更する必要があります。検証メッセージコードブロックを追加および削除するカスタムディレクティブを作成するには、option(以下のデモ)。

検証メッセージの表示/非表示

ディレクティブでは、ディレクティブのホストフォームコントロールにアクセスし、valueChangesイベントにサブスクライブすることで、その検証ステータスに基づいて検証メッセージを追加/削除できます。

@Directive(...)
export class ValidatorMessageDirective implements OnInit {

  constructor(
    private container: ControlContainer,
    private elem: ElementRef,          // Host dom element
    private control: NgControl         // Host form control
  ) { }

  ngOnInit() {
    const control = this.control.control;

    control.valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
      this.option.forEach(validate => {
        if (control.hasError(validate.type)) {
          const validateMessageElem = document.getElementById(validate.id);
          if (!validateMessageElem) {
            const divElem = document.createElement('div');
            divElem.innerHTML = validate.message;
            divElem.id = validate.id;
            this.elem.nativeElement.parentNode.insertBefore(divElem, this.elem.nativeElement.nextSibling);
          }
        } else {
          const validateMessageElem = document.getElementById(validate.id);
          if (validateMessageElem) {
             this.elem.nativeElement.parentNode.removeChild(validateMessageElem);
          }
        }
      })
    });
  }
}

オプションの検証

このディレクティブは、対応する検証エラーに基づいて検証メッセージを追加および削除します。したがって、最後に行うべき手順は、どの検証エラーのタイプを監視し、どのメッセージを表示するかをディレクティブに伝えることです。これは、検証オプションをディレクティブに転送する@Inputフィールドです。


次に、以下のようにテンプレートコードを簡単に記述できます。

<form [formGroup]="form">
  <input type="text" formControlName="test" [validate-message]="testValidateOption"><br/>
  <input type="number" formControlName="test2" [validate-message]="test2ValidateOption">
</form>

作業を参照してくださいデモ.

1
Pengyy

これを使用できます repo これにはデフォルトの検証メッセージがあり、カスタマイズすることもできます

使用例は次のようになります

<form [formGroup]="editorForm" novalidate>
    <label>First Name</label>
    <input formControlName="firstName" type="text">
    <ng2-mdf-validation-message [control]="firstName" *ngIf="!firstName.pristine"></ng2-mdf-validation-message>
</form>
1
Reza