Gérer les erreurs de forme à l'aide de composants Angular 5-TypeScript

<!-Je travaille actuellement sur un formulaire en angulaire 5/ dactylographié plusieurs champs (plus de 10 champs), et je voulais gérer les erreurs plus correctement sans duplication de code dans ma page html.

Voici un exemple d'un formulaire :

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

Dans mon cas, j'ai deux types de validation de mon formulaire :

  • validation Html: requis, maxSize,... etc.
  • validation arrière: pour exemple, compte non valide, taille du fichier chargé, ... etc.

j'essaie d'utiliser une directive comme mentionné ici

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

mais même avec cette solution le code est toujours dupliqué et aucune capacité à gérer les deux types de validation.

avez-vous une autre approche ? Est d'utiliser des composants appropriés dans ce cas ? Si oui, comment le faire.

je vous Remercie d'avance pour votre investissement.

19
demandé sur Lyes CHIOUKH 2018-04-10 12:54:59

9 réponses

vous pouvez déplacer les erreurs de validation dans un component et passer dans formControl.les erreurs comme une entrée de la propriété. De cette façon, tous les messages de validation peuvent être réutilisés. Voici un exemple sur StackBlitz. Le code utilise un matériau angulaire, mais il devrait quand même être pratique même si vous ne l'êtes pas.

validation-erreurs.composant.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-erreurs.composant.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>

pour la validation arrière les messages positionnent l'erreur manuellement sur le contrôle de formulaire.

const nameControl = this.userForm.get('name');
nameControl.setErrors({
  "notUnique": true
});

utilisation De la validation de la composante sur la forme:

   <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>
8
répondu JayChase 2018-05-13 08:19:43

Démo

Vous pouvez injecter NgForm et accédez à la FormControlName la directive @ContentChild dans un composant de validateur personnalisé pour obtenir la réutilisation:

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

}

pour l'utiliser, vous envelopperiez tous vos contrôles de formulaire (qui a un formControlName) avec un élément HTML et ajouteriez un attribut validator:

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

ceci fonctionnera pour les validateurs synchrones et asynchrones.

3
répondu pixelbits 2018-05-08 06:44:01

j'avais la même exigence, personne n'aime réécrire le même code deux fois.

cela peut être fait en créant des contrôles de formulaires personnalisés. L'idée est de créer vos contrôles de formulaires personnalisés , d'avoir un service commun qui génère un objet formControl personnalisé et d'injecter des validateurs appropriés basés sur le type de données fourni dans L'objet FormControl.

d'Où vient le type de Données proviennent de ?

avoir un fichier dans vos actifs ou n'importe où qui contient des types comme ceci :

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

   }
}
]

Ce que vous pouvez lire dans votre ValidatorService et sélectionnez le type de données approprié avec lequel vous pouvez créer vos validateurs et retourner à votre contrôle de formulaire personnalisé.

Par Exemple ,

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

Voici une brève description de ce que j'ai fait pour y arriver. Si vous avez besoin d'informations supplémentaires , faites un commentaire. Je ne peux donc pas vous fournir de base de code en ce moment, mais demain pourrait mettre à jour le réponse.

mise à JOUR pour l'Erreur Montrant la partie

vous pouvez faire 2 choses pour elle, lier le validateur de votre formControl avec un div dans le contrôle et le basculer avec *ngIf="formControl.hasError('required)"', etc.

pour qu'un Message / Erreur soit affiché dans un autre endroit générique comme un forum de messages il est préférable de mettre ce markup de Forum de messages quelque part dans le composant parent qui ne soit pas enlevé pendant le routage (discutable basé sur l'exigence) et faire que composant écouter un MessageEmit événement qui votre ErrorStateMatcher de votre formControl tirera chaque fois que nécessaire(en fonction des besoins).

C'est le design que nous avons utilisé et il a assez bien fonctionné , vous pouvez faire beaucoup avec ces formControls une fois que vous commencez à les personnaliser.

2
répondu Aakash Uniyal 2018-05-10 09:34:22

Vous pouvez créer un composant personnalisé ValidationMessagesComponent:

Modèle :

<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

Et avec les entrées :

@Input() controlName;
@Input() form;

Puis l'utiliser comme ceci :

<validation-messages [form]="myForm" controlName="email"></validation-messages>
1
répondu ibenjelloun 2018-05-06 14:13:28

pour la validation html j'écrirais un FormControl personnalisé qui sera essentiellement un enveloppeur autour d'une entrée. J'écrirais aussi des validateurs personnalisés qui renverraient un message d'erreur (les validateurs intégrés renverraient un objet que je crois). Dans votre formcontrol personnalisé, vous pouvez faire quelque chose comme ceci:

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

pour le validateur d'arrière-plan vous pouvez écrire un async validateur.

1
répondu Robin Dijkhof 2018-05-07 19:33:13

Vous pouvez utiliser ce repo qui a des messages de validation par défaut et vous pouvez les personnaliser aussi

exemple d'utilisation sera comme ceci

<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>
1
répondu RezaRahmati 2018-05-07 19:48:14

pour rendre le code de modèle clair et éviter la duplication du code de validation des messages, nous devrions les changer pour être plus réutilisables, ici la création d'une directive personnalisée qui ajoute et supprime le bloc de code de message de validation est une option (indiqués ci-dessous la démo).

Afficher/Masquer la validation des messages

dans la directive, Nous pouvons accéder au contrôle du formulaire hôte de la directive et Ajouter/Supprimer un message de validation basé sur le statut de validation de celui-ci en souscrivant à valueChanges événement.

@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);
          }
        }
      })
    });
  }
}

valider les options

la directive ajoute et supprime les messages de validation basés sur les erreurs correspondantes de validation. Donc la dernière étape que nous devrions faire est de dire à la directive quels types d'erreurs valider à regarder et quels messages devraient être affichés, c'est le @Input champ par lequel nous transportons les options de validation à la directive.


alors nous pouvons simplement écrire le code du modèle comme ci-dessous:

<form [formGroup]="form">
  <input type="text" formControlName="test" [validate-message]="testValidateOption"><br/>
  <input type="number" formControlName="test2" [validate-message]="test2ValidateOption">
</form>

Consulter de travail démo.

1
répondu Pengyy 2018-05-10 05:25:58

la meilleure façon est d'implémenter custom ControlValueAccessors pour chaque type d'entrée, combinant <label>, <input> et quelques balises pour afficher le message d'erreur (dans mon projet j'utilise simplement title l'attribut à cet effet) dans un seul composant.

tous les accesseurs de valeurs doivent implémenter la même interface ou étendre la classe abstract de base, en fournissant des méthodes pour définir et clarifier le message d'erreur et toutes les autres méthodes que vous pouvez appeler à partir des directives validator.

Aussi, vous aurez besoin d'implémenter des directives de validateur personnalisées pour chaque type de validation (j'ai dû ré-implémenter même required et maxlength), les validateurs doivent renvoyer les objets d'erreur de manière uniforme, c.-à-d. pour le validateur de courriel {email: "Invalid email address"}. Les directives Validator peuvent obtenir des références à vos accesseurs de valeurs de contrôle via injection -@Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[] (généralement de tableau avec un élément AbstractFormComponent est votre classe de base pour les accesseurs), utilisez cette référence pour définir ou effacer le message d'erreur d'accesseur.

vous pouvez également mettre en œuvre deux d'autres types de directives de validateur: sync et async, qui peuvent recevoir la fonction de validateur via @Input i.e. [async]="loginValidatorFn", où loginValidatorFn est défini dans la classe component et retourne Observable<ValidationErrors>.

C'est vrai code de notre application:

<div class="input" [caption]="'SSN: '" name="ssn" type="text" [(ngModel)]="item.ssn" [async]="memberSsnValidatorFn" required></div>
1
répondu kemsky 2018-05-11 22:00:32

Voici une partie du code que j'ai utilisé dans la bibliothèque pour générer des formes dynamiques.

C'est FormError.ts qui est utilisé pour obtenir des messages d'erreur et personnalisés si nous voulons.

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";
}

Messages Personnalisés

 class FormError {
    constructor(private errorGetter?: ErrorGetter) { }
    }

ErrorGetter

type ErrorFunction = (errorName: string, error: object) => string;
type ErrorGetter =
    string | { [key2: string]: string } | ErrorFunction;
  1. si nous voulons une erreur constante pour n'importe quelle erreur, alors il devrait être comme

    new FormError('Password is not right')

  2. Si nous voulons erreur constante pour erreur spécifique alors il devrait être comme

    new FormError({required:'Address is necessary.'})

    pour les autres erreurs, il va dans prédire l'erreur.

  3. si nous voulons utiliser la fonction pour une erreur spécifique, alors elle devrait être comme

    new FormError((errorName,errorObject)=>{ if(errorName=='a') return '2';})

    pour les autres erreurs, il va dans prédire l'erreur.

  4. Modifiez la fonction de prédicteur en fonction de vos besoins.

FormError composante

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

Utilisation

<form-error [control]='thatControl' ></form-error>

Évidemment FormError n'est pas le meilleur design. Modifier comme vous le souhaitez.

1
répondu amit77309 2018-05-13 09:50:35