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