使用组件Angular - TypeScript处理表单错误

时间:2018-04-10 09:55:00

标签: javascript html angular typescript angular5

我目前正在处理几个字段(超过10个字段)的Angular / Typescript中的表单,我想更正确地管理错误,而不会在我的html页面中重复代码。< / p>

以下是表单的示例:

<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,......等。
  • 返回验证:例如,帐户无效,已加载文件的大小等......

我尝试使用指令as mentioned here

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

但即使使用此解决方案,代码也总是重复,无法处理这两种类型的验证。

你有另一种方法吗?在这种情况下使用适当的组件吗?如果是的话,怎么做呢。

提前感谢您的投资。

11 个答案:

答案 0 :(得分:17)

您可以将验证错误移动到组件中,并将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>

答案 1 :(得分:4)

Demo

您可以在自定义验证程序组件中注入NgForm并通过FormControlName访问@ContentChild指令以实现重复使用:

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

这适用于同步和异步验证器。

答案 2 :(得分:2)

我有同样的要求,没有人喜欢两次重写相同的代码。

这可以通过创建自定义表单控件来完成。 我们的想法是创建自定义表单控件,具有生成自定义formControl对象的公共服务,并根据FormControl对象中提供的数据类型注入适当的Validators。

数据类型来自哪里?

在您的资源或任何包含以下类型的文件中包含文件:

[{
  "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做很多事情。

答案 3 :(得分:1)

您可以创建自定义组件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>

答案 4 :(得分:1)

对于html验证,我会写一个custom formcontrol,它基本上是一个输入的包装器。我还会编写返回错误消息的自定义验证器(内置验证器返回我认为的对象)。在您的自定义表单控件中,您可以执行以下操作:

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

对于后端验证器,您可以编写async validator

答案 5 :(得分:1)

您可以使用具有默认验证消息的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>

答案 6 :(得分:1)

为了使模板代码清晰并避免重复验证消息的代码,我们应该将它们更改为更可重用,这里创建一个自定义指令,添加和删除验证消息代码块是一个选项(如下面演示< / em>的)。

显示/隐藏验证消息

在指令中,我们可以访问指令&#39;主机表单控件,并通过订阅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>

请参阅 demo

答案 7 :(得分:1)

最好的方法是为每种类型的输入实现自定义ControlValueAccessor,合并<label><input>和一些用于显示错误消息的标记(在我的项目中,我只是使用{{ 1}}用于此目的的属性)在单个组件中。

所有值访问器应实现相同的接口或扩展基本抽象类,提供设置和清除错误消息的方法以及您可能希望从验证器指令调用的任何其他方法。

此外,您需要为每种验证类型实现自定义验证程序指令(我必须重新实现titlerequired),验证程序必须以统一的方式返回错误对象,即电子邮件验证程序maxlength。 Validator指令可以通过注入引用您的控件值访问器 - {email: "Invalid email address"}(通常带有一个元素的数组,@Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[]是访问者的基类),使用此引用来设置或清除访问者错误消息。

您还可以实现另外两种类型的验证器指令:sync和async,它们可以通过AbstractFormComponent接收验证器函数,即@Input,其中[async]="loginValidatorFn"在组件类中定义并返回{ {1}}。

这是我们应用程序的真实代码:

loginValidatorFn

答案 8 :(得分:1)

以下是我在库中用于生成动态表单的代码的一部分。

这是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函数。

  5. 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不是最好的设计。但是你喜欢修改。

答案 9 :(得分:0)

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

答案 10 :(得分:0)

您可以使用 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])
 });

玩转here