【问题标题】:Handle form errors using components Angular - TypeScript使用组件 Angular - TypeScript 处理表单错误
【发布时间】:2021-06-01 01:51:22
【问题描述】:

我目前正在处理Angular/Typescript 中的多个字段(超过 10 个字段)的表单,并且我想更正确地管理错误,而无需在我的 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 等。
  • 返回验证:例如,无效的帐户、加载文件的大小等。

我尝试使用指令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>

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

您还有其他方法吗?在这种情况下使用组件是否合适?如果可以,怎么做。

提前感谢您的投资。

【问题讨论】:

标签: javascript html angular typescript angular5


【解决方案1】:

您可以将验证错误移动到组件中,并将 formControl.errors 作为输入属性传递。这样所有验证消息都可以重复使用。这是StackBlitz 的示例。该代码使用了 Angular Material,但即使您不是,也应该很方便。

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>

【讨论】:

  • 感谢您的回复。我有一个小问题:如果我想为“必需”错误编写多个错误消息怎么办? 必需名称 文件是必需的ng-容器>
  • 您可以在验证组件上使用前缀作为输入,这将允许进行一些自定义。 stackblitz.com/edit/angular-bcm51p
  • 这是一种非常干净的方法。我喜欢它。太棒了!
  • @JayChase 当我使用类似这样的东西时,我的页面崩溃了很多。知道它可能是什么以及如何解决它吗?还有其他方法吗?
  • 完美答案感谢您节省了我的时间:)
【解决方案2】:

Demo

您可以在自定义验证器组件中注入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>

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

【讨论】:

  • 嗨@pixelbits:谢谢您的回复。如何使用这种方法检查非 html 错误(对于我的背部错误)?
【解决方案3】:

我也有同样的需求,没有人喜欢把同样的代码重写两次。

这可以通过创建自定义表单控件来完成。 这个想法是您创建自定义表单控件,拥有一个生成自定义 formControl 对象的通用服务,并根据提供给 FormControl 对象的数据类型注入适当的验证器。

Data 类型从何而来?

在你的资产或任何地方有一个文件包含这样的类型:

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

   }
}
]

您可以在ValidatorService 中阅读此内容并选择适当的数据类型,您可以使用它创建验证器并返回自定义表单控件。

例如,

&lt;ui-text name="name" datatype="nameType" [(ngModel)]="data.name"&gt;&lt;/ui-text&gt;

这是我为实现这一目标所做的高层次的简要描述。如果您需要更多信息,请发表评论。我出去了,所以现在无法为您提供代码库,但明天的某个时候可能会更新答案。

错误显示部分的更新

您可以为它做两件事,将您的 formControl 的验证器与控件中的 div 绑定,并使用 *ngIf="formControl.hasError('required)"` 等进行切换。

要在另一个通用位置(如消息板)显示消息/错误,最好将该消息板标记放在 ParentComponent 中的某个位置,该标记在路由时不会被删除(根据要求有争议)并使该组件监听一个 MessageEmit 事件,您的 formControl 的 ErrorStateMatcher 将在必要时触发(根据要求)。

这是我们使用的设计,效果很好,一旦开始自定义这些表单控件,您可以做很多事情。

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 感谢您的回复。如何使用此组件管理“非 htlm 元素”?对于我的问题中提到的“返回错误”。此错误不在我的表单中。
      • 当后端调用失败时,你的后端应该发送一个可以被你的自定义组件解释的错误代码列表。这是一个stackblitz示例stackblitz.com/edit/stackoverflow-49750645
      • 感谢您的回复。我发现这个解决方案非常有趣且易于设置,我会尝试一下
      【解决方案6】:

      你可以使用这个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>
      

      【讨论】:

      • 感谢您的回复。我需要在不使用外部库的情况下执行验证。
      【解决方案7】:

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

      显示/隐藏验证消息

      在指令中,我们可以通过订阅它的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

      【讨论】:

        【解决方案8】:

        最好的方法是为每种类型的输入实现自定义ControlValueAccessors,结合&lt;label&gt;&lt;input&gt;和一些用于显示错误消息的标签(在我的项目中,我只是为此目的使用title属性)在单个组件中。

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

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

        您还可以实现另外两种类型的验证器指令:同步和异步,它们可以通过@Input[async]="loginValidatorFn" 接收验证器函数,其中loginValidatorFn 在组件类中定义并返回Observable&lt;ValidationErrors&gt;

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

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

        【讨论】:

        • 感谢您的回复。我觉得这个解决方案很有趣。
        【解决方案9】:

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

        这是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)=&gt;{ 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 不是最好的设计。随意修改。

        【讨论】:

        • 我在发帖前修改了部分代码可能会出现一些问题。
        • 感谢您的回复。我觉得这个解决方案很有趣,但是对于简单的表单来说,它或多或少有些复杂。
        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          您可以使用NPM 包。它简单易用,可针对反应式和模板驱动的表单进行自定义。

          代码sn-p:

          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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-17
            • 2018-03-25
            • 1970-01-01
            • 2019-01-30
            • 1970-01-01
            • 2019-06-19
            相关资源
            最近更新 更多