web-dev-qa-db-fra.com

Angular 5, Angular Material: la validation de Datepicker ne fonctionne pas

J'utilise le dernier matériau angulaire et le dernier matériau angulaire. J'ai un datepicker et je veux ajouter une validation. Les documents indiquent que l'attribut required devrait fonctionner immédiatement, mais il ne semble pas gérer les erreurs de la même manière que les autres éléments de formulaire.

Voici ma note:

<mat-form-field class="full-width">
    <input matInput [matDatepicker]="dob" placeholder="Date of birth" [(ngModel)]="myService.request.dob" #dob="ngModel" required app-validateAdult>
    <mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
    <mat-datepicker #dob></mat-datepicker>
    <mat-error *ngIf="dob.errors && dob.errors.required">Your date of birth is required</mat-error>
</mat-form-field>

Cela fonctionne sur le chemin d'accès heureux. Ainsi, lorsqu'une date est sélectionnée, la date se termine dans la propriété attendue dans myService.

La validation ne fonctionne pas comme je l’attendais cependant; dans ce cas, si je clique sur le champ, puis que je le quitte sans entrer de date, l'entrée fait obtient un style rouge, mais l'objet [controlName].errors habituel n'est pas rempli. Cela signifie que l'affichage d'un message d'erreur de la manière habituelle (la manière qui fonctionne avec d'autres entrées qui ne sont pas des sélecteurs de date sur la même page) ne fonctionne pas:

<mat-error *ngIf="dob.errors && dob.errors.required">Your date of birth is required</mat-error>

Le *ngIf n'est jamais vrai parce que le sélecteur de date ne semble jamais mettre à jour le dob.errors; le message d'erreur n'est donc jamais affiché, même lorsque l'entrée est stylée de manière non valide.

Est-ce correct? Ai-je raté quelque chose?

J'ai également essayé d'ajouter une directive personnalisée pour valider que la date sélectionnée avec datepicker indique que l'utilisateur a plus de 18 ans:

export class AdultValidator implements Validator {
  constructor(
    @Attribute('app-validateAdult') public validateAdult: string
  ) { }

  validate(control: AbstractControl): { [key: string]: any } {
    const dob = control.value;
    const today = moment().startOf('day');
    const delta = today.diff(dob, 'years', false);

    if (delta <= 18) {
      return {
        validateAdult: {
          'requiredAge': '18+',
          'currentAge': delta
        }
      };
    }

    return null;
  }
}

Dans ce cas, j'essaie d'utiliser une matError similaire (à la place liée à dob.errors.validateAdult) pour afficher l'erreur le cas échéant.

La chose intéressante avec ceci est que si je choisis une date il y a moins de 18 ans, toute l'entrée, l'étiquette, etc., obtient le style d'erreur rouge par défaut, donc quelque chose se passe, mais je ne vois toujours pas mon message d'erreur.

Toutes les suggestions seraient très appréciées!

Versions exactes:

Angular CLI: 1.6.3
Node: 6.11.0
OS: win32 x64
Angular: 5.1.3
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

@angular/cdk: 5.0.4
@angular/cli: 1.6.3
@angular/flex-layout: 2.0.0-beta.12
@angular/material-moment-adapter: 5.0.4
@angular/material: 5.0.4
@angular-devkit/build-optimizer: 0.0.36
@angular-devkit/core: 0.0.22
@angular-devkit/schematics: 0.0.42
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.9.3
@schematics/angular: 0.1.11
@schematics/schematics: 0.0.11
TypeScript: 2.4.2
webpack: 3.10.0
9
danwellman

J'ai réussi à le faire fonctionner sans utiliser la ErrorStateMatcher, bien que cela m'a aidé à trouver la solution. Partir d’ici pour référence future ou pour aider les autres.

J'ai converti mon formulaire en un formulaire réactif au lieu d'un formulaire basé sur un modèle, et j'ai remplacé la directive de validation personnalisée par un validateur plus simple (non directive).

Voici le code de travail:

my-form.component.html:

<div class="container" fxlayoutgap="16px" fxlayout fxlayout.xs="column" fxlayout.sm="column" *ngIf="fieldset.controls[control].type === 'datepicker'">
  <mat-form-field class="full-width" fxflex>
    <input matInput 
           [formControlName]="control"
           [matDatepicker]="dob"
           [placeholder]="fieldset.controls[control].label" 
           [max]="fieldset.controls[control].validation.max">
    <mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
    <mat-datepicker #dob></mat-datepicker>
    <mat-error *ngIf="myForm.get(control).hasError('required')">
      {{fieldset.controls[control].validationMessages.required}}</mat-error>
    <mat-error *ngIf="myForm.get(control).hasError('underEighteen')">
      {{fieldset.controls[control].validationMessages.underEighteen}}
    </mat-error>
  </mat-form-field>
</div>

note : Le code ci-dessus se trouve dans un couple de boucles ngFor imbriquées qui définissent la valeur de fieldset et control. Dans cet exemple, control correspond à la chaîne dob.

plus de dix-huit.validator.ts:

import { ValidatorFn, AbstractControl } from '@angular/forms';
import * as moment from 'moment';

export function overEighteen(): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } => {
    const dob = control.value;
    const today = moment().startOf('day');
    const delta = today.diff(dob, 'years', false);

    if (delta <= 18) {
      return {
        underEighteen: {
          'requiredAge': '18+',
          'currentAge': delta
        }
      };
    }

    return null;
  };
}

my-form.component.ts:

buildForm(): void {
  const formObject = {};

  this.myService.request.fieldsets.forEach((controlsGroup, index) => {

    this.fieldsets.Push({
      controlNames: Object.keys(controlsGroup.controls)
    });

    for (const control in controlsGroup.controls) {
      if (controlsGroup.controls.hasOwnProperty(control)) {
        const controlData = controlsGroup.controls[control];
        const controlAttributes = [controlData.value];
        const validators = [];

        if (controlData.validation) {
          for (const validator in controlData.validation) {
            if (controlData.validation.hasOwnProperty(validator)) {
              if (validator === 'overEighteenValidator') {
                validators.Push(this.overEighteenValidator);
              } else {
                validators.Push(Validators[validator]);
              }
            }
          }
          controlAttributes.Push(Validators.compose(validators));
        }

        formObject[control] = controlAttributes;
      }
    }
  });

  this.myForm = this.fb.group(formObject);
}
2
danwellman

J'utilise ErrorStateMatcher dans mes formes matérielles angulaires, cela fonctionne parfaitement.

Vous devriez avoir un code qui ressemble à ça:

<mat-form-field class="full-width">
    <input matInput [matDatepicker]="dob" placeholder="Date of birth" formControlName="dob" required app-validateAdult>
    <mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
    <mat-datepicker #dob></mat-datepicker>
    <mat-error *ngIf="dob.hasError('required')">Your date of birth is required</mat-error>
</mat-form-field>

Et TypeScript:

import { ErrorStateMatcher } from '@angular/material/core';

export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(
    control: FormControl | null,
    form: FormGroupDirective | NgForm | null
  ): boolean {
    const isSubmitted = form && form.submitted;
    return !!(
      control &&
      control.invalid &&
      (control.dirty || control.touched || isSubmitted)
    );
  }
}


export class AdultValidator implements Validator {
  dob = new FormControl('', [
    Validators.required
  ]);

  matcher = new MyErrorStateMatcher();
}

Vous pouvez en voir plus ici: https://material.angular.io/components/input/overview

4
Manon Ingrassia

Ajoutez ceci dans votre page de vue: 

      <mat-form-field>
     <input matInput [matDatepicker]="dp"   placeholder="Employement date" >
   <mat-datepicker-toggle matSuffix [for]="dp"></mat-datepicker-toggle>
   <mat-datepicker #dp></mat-datepicker>
    </mat-form-field>

Importez simplement MatDatepickerModule, MatNativeDateModule dans votre module

1
Ajai

Vous pouvez changer le nom de la référence d’entrée comme ci-dessous .. Notez que l'élément d'entrée #dobInput est référencé uniquement dans mat-error. 

 <mat-form-field class="full-width">
<input matInput [matDatepicker]="dob" placeholder="Date of birth" [(ngModel)]="myService.request.dob" #dobInput="ngModel" required app-validateAdult>
<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
<mat-datepicker #dob></mat-datepicker>
<mat-error *ngIf="dobInput.errors && dobInput.errors.required">Your date of birth is required</mat-error>

Le sélecteur est référencé par #dbo 

[matDatepicker]="dob"

<mat-datepicker-toggle matSuffix [for]="dob"></mat-datepicker-toggle>
1
Leonardo Neninger

Vous avez le #dob dupliqué. Cela peut avoir un comportement indésirable dans la validation angulaire.

Tu as 

<input #dob='ngModel'

et 

<mat-datepicker #dob></mat-datepicker>

Veuillez corriger la convention de nommage et voir ce qui se passe.

1
Leonardo Neninger

Avez-vous essayé de définir le * ngIf sur votre validateur personnalisé comme ci-dessous:

 <mat-error *ngIf="dob.errors && dob.errors.validateAdult">Your date of birth 
 is less than 18 ?</mat-error>

Si cela fonctionne, vous pouvez créer un autre validateur pour simuler le comportement de validation natif requis.

export class CustomRequireValidator implements Validator {
  constructor(
    @Attribute('app-validateRequired') public validateRequired: string
  ) { }

  validate(control: AbstractControl): { [key: string]: any } {
    let value = control.value;
    if (!value || value == null || value.toString().length == 0) {
        return requireValidator: {
      'requiredAge': 'This field is required',
    }
    }

    return null;
  }
}

Et utilisez ensuite le ngIf précédent comme ci-dessous:

 <mat-error *ngIf="dob.errors && dob.errors.requireValidator">Your date of 
birth is less than 18 ?</mat-error>

Je ne l'ai pas testé, mais cela devrait fonctionner logiquement.

0
Nour