Обрабатывать ошибки формы с помощью компонентов Angular 5-TypeScript

в настоящее время я работаю над формой в Угловое 5/машинопись нескольких полей (более 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,... так далее.
  • назад проверка : Например, недопустимая учетная запись, размер загруженного файла,... так далее.

Я пытаюсь использовать директиву как уже упоминалось здесь

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

но даже с этим решением код всегда дублируется и не может обрабатывать оба типа проверки.

у вас есть другой подход ? Подходит ли в данном случае использование компонентов ? Если да, то как это сделать.

заранее спасибо за ваши инвестиции.

9 ответов


вы можете переместить ошибки проверки в компонент и передать в formControl.ошибки как свойство ввода. Таким образом, все сообщения проверки могут быть повторно использованы. Вот пример на StackBlitz. Код использует угловой материал, но все равно должен быть удобен, даже если вы этого не делаете.

проверка-ошибки.деталь.ТС

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

}

проверка-ошибки.деталь.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>

для обратной проверки сообщения установите ошибку вручную в элементе управления form.

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


у меня было такое же требование, никто не любит переписывать один и тот же код дважды.

Это можно сделать, создав пользовательские элементы управления формы. Идея заключается в том , что вы создаете пользовательские элементы управления формой, имеете общую службу, которая генерирует пользовательский объект formControl и вводите соответствующие валидаторы на основе типа данных, предоставленного в объект FormControl.

откуда взялся тип данных ?

имейте файл в ваших активах или в любом месте, который содержит типы вот так:

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

   }
}
]

об этом вы можете прочитать в ValidatorService и выберите соответствующий тип данных, с помощью которого можно создать валидаторы и вернуться к пользовательскому элементу управления формой.

Например ,

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

это краткое описание его на высоком уровне того, что я сделал для достижения этого. Если вам нужна дополнительная информация об этом, прокомментируйте. Я не могу предоставить вам базу кода прямо сейчас, но когда-нибудь завтра может обновить ответ.

обновление для ошибки, показывающей часть

вы можете сделать для него 2 вещи, связать валидатор вашего formControl с div в элементе управления и переключить его с *ngIf="formControl.hasError('required)"', etc.

для сообщения / ошибки, которая будет отображаться в другом общем месте, таком как доска объявлений, лучше поместить эту разметку доски сообщений где-нибудь в ParentComponent, который не удаляется во время маршрутизации (спорный на основе требования) и сделать это компонент слушайте MessageEmit событие, которое ваш ErrorStateMatcher вашего formControl будет срабатывать при необходимости (на основе требования).

это дизайн, который мы использовали , и он работал довольно хорошо, вы можете многое сделать с этими formControls, как только вы начнете их настраивать.


вы можете создать пользовательский компонент 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>

для проверки html я бы написал пользовательские formcontrol который в основном будет оберткой вокруг ввода. Я бы также написал пользовательские валидаторы, которые возвращают сообщение об ошибке (встроенные валидаторы возвращают объект, который я считаю). В вашем пользовательском formcontrol вы можете сделать что-то вроде этого:

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

для проверки backend вы можете написать асинхронный проверки.


вы можете использовать этот РЕПО который имеет сообщения проверки по умолчанию, и вы можете настроить их, а также

пример использования будет такой

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

чтобы сделать код шаблона понятным и избежать дублирования кода проверки сообщений, мы должны изменить их, чтобы они были более многоразовыми, здесь создание пользовательской директивы, которая добавляет и удаляет блок кода проверки сообщений, является опцией (показано ниже demo).

Показать / Скрыть подтверждающие сообщения

в директиве мы можем получить доступ к управлению формой хоста директивы и добавить / удалить проверяющее сообщение на основе его статуса проверки, подписавшись на это 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>

см. Работает демо.


лучший способ-реализовать custom ControlValueAccessors для каждого типа ввода, комбинируя <label>, <input> и некоторые теги для отображения сообщения об ошибке (в моем проекте я просто использовать title атрибут для этой цели) в одном компоненте.

все методы доступа к значениям должны реализовывать один и тот же интерфейс или расширять базовый абстрактный класс, предоставляя методы для установки и очистки сообщения об ошибке и любые другие методы, которые вы можете вызвать из директив валидатора.

также, вам нужно будет реализовать пользовательские директивы валидатора для каждого типа проверки (мне пришлось повторно реализовать даже required и maxlength), валидаторы должны возвращать объекты ошибок единым способом, т. е. для email validator {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 согласно вашей потребности.

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 не самый лучший дизайн. Измените, как вам нравится.