簡體   English   中英

使用 Angular 組件處理表單錯誤 - TypeScript

[英]Handle form errors using components 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>

就我而言,我的表單有兩種類型的驗證:

  • 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>

但即使使用此解決方案,代碼也始終是重復的,並且無法處理這兩種類型的驗證。

你有另一種方法嗎? 在這種情況下使用組件是否合適? 如果是,怎么辦。

預先感謝您的投資。

您可以將驗證錯誤移動到組件中,並將 formControl.errors 作為輸入屬性傳入。 這樣,所有驗證消息都可以重復使用。 這是一個關於StackBlitz的例子。 該代碼正在使用 Angular Material,但即使您不是,也應該很方便。

驗證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() {}

}

驗證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>

演示

您可以在自定義驗證器組件中注入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() { }

}

要使用它,你需要用一個 HTML 元素包裝所有的表單控件(它有一個 formControlName)並添加一個驗證器屬性:

<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>

這適用於同步和異步驗證器。

對於 html 驗證,我將編寫一個自定義表單控件,它基本上是輸入的包裝器。 我還會編寫返回錯誤消息的自定義驗證器(內置驗證器返回一個我相信的對象)。 在您的自定義表單控件中,您可以執行以下操作:

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

對於后端驗證器,您可以編寫一個異步驗證器

我有同樣的要求,沒有人喜歡兩次重寫相同的代碼。

這可以通過創建自定義表單控件來完成。 這個想法是你創建你的自定義表單控件,有一個通用的服務來生成一個自定義的 formControl 對象並根據提供給 FormControl 對象的數據類型注入適當的驗證器。

數據類型從何而來?

在您的資產或任何包含以下類型的文件中放置一個文件:

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

   }
}
]

這可以在您的ValidatorService讀取並選擇適當的數據類型,您可以使用它來創建您的驗證器並返回到您的自定義表單控件。

例如,

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

這是對我為實現這一目標所做的工作的簡要描述。 如果您需要與此有關的其他信息,請發表評論。 我出去了,所以現在無法為您提供代碼庫,但明天某個時候可能會更新答案。

錯誤顯示部分的更新

您可以為它做兩件事,將您的 formControl 的驗證器與控件中的 div 綁定,並使用*ngIf="formControl.hasError('required )"` 等進行切換。

要將消息/錯誤顯示在另一個通用位置(如消息板)中,最好將該消息板標記放在 ParentComponent 中的某個位置,該標記在路由時不會被刪除(根據需求存在爭議)並使該組件偵聽 MessageEmit 事件您的 formControl 的ErrorStateMatcher將在必要時觸發(根據要求)。

這是我們使用的設計並且效果很好,一旦您開始自定義它們,您就可以使用這些 formControl 做很多事情。

您可以創建一個自定義組件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>

你可以使用這個具有默認驗證消息的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>

為了使模板代碼清晰並避免驗證消息的重復代碼,我們應該將它們更改為更可重用,這里創建一個添加和刪除驗證消息代碼塊的自定義指令是一個選項(如下面的演示所示)。

顯示/隱藏驗證消息

在指令中,我們可以通過訂閱它的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>

參考工作演示

最好的方法是為每種類型的輸入實現自定義ControlValueAccessor ,在單個組件中結合<label><input>和一些用於顯示錯誤消息的標簽(在我的項目中,我只是為此目的使用title屬性)。

所有值訪問器都應該實現相同的接口或擴展基抽象類,提供設置和清除錯誤消息的方法以及您可能希望從驗證器指令調用的任何其他方法。

此外,您需要為每種驗證類型實現自定義驗證器指令(我不得不重新實現甚至requiredmaxlength ),驗證器必須以統一的方式返回錯誤對象,即電子郵件驗證器{email: "Invalid email address"} 驗證器指令可以通過注入獲取對您的控件值訪問器的引用 - @Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[] (通常是一個元素的數組, AbstractFormComponent是訪問器的基類),使用此引用來設置或清除訪問器錯誤信息。

您還可以實現另外兩種類型的驗證器指令:sync 和 async,它們可以通過@Input接收驗證器函數,即[async]="loginValidatorFn" ,其中loginValidatorFn在組件類中定義並返回Observable<ValidationErrors>

這是我們應用程序中的真實代碼:

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

這是我在庫中用於生成動態表單的部分代碼。

這是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 函數。

表單錯誤組件

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不是最好的設計。 隨心所欲地修改。

 <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>

您可以使用NPM包。 它簡單易用,可針對反應式和模板驅動的表單進行自定義。

代碼片段:

HTML

<form [formGroup]="demoForm">
    <div>
         <label for="name">Name</label>
         <input type="text" formControlName="name" name="name" placeholder="Name validator">
         <tn-form-error [control]="demoForm.controls.name" [field]="'Name'"></tn-form-error>
    </div>
</form>

組件

<p>
 this.demoForm = new FormGroup({
      name: new FormControl(''[Validators.required])
 });

這里玩耍

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM