web-dev-qa-db-fra.com

Angular 2/Material - md-error non affiché avec un validateur personnalisé appliqué au groupe de formulaires parent

Je ne parviens pas à afficher les erreurs de validation sur un formulaire basé sur un modèle avec Angular (v4.3.6). 

Dans mon modèle, j'ai les éléments suivants:

this.registerForm = formBuilder.group({
        'email':[null,Validators.compose([Validators.required, ValidateEmail])],
        'firstName':[null, Validators.required],
        'lastName':[null, Validators.required],
        'passwordGroup': formBuilder.group({
            'password':[null, Validators.compose([Validators.required,Validators.minLength(8)])],
            'passwordConfirmation':[null, Validators.required],
        },{validator: ValidatePasswordConfirmation})
    });

Le validateur personnalisé ValidatePasswordConfirmation référencé est le suivant:

export function ValidatePasswordConfirmation(group: FormGroup) {

    if(group.value.password !== group.value.passwordConfirmation){
        return { 'no-match':true };
    }

    return null;
}

Enfin, dans mon modèle, j'ai les éléments suivants:

<md-form-field>
  <input mdInput name="passwordConfirmation" placeholder="Confirm password" [formControl]="registerForm.controls['passwordGroup'].controls['passwordConfirmation']" [(ngModel)]="model.passwordConfirmation" type="password">

<md-error *ngIf="registerForm.controls['passwordGroup'].controls['passwordConfirmation'].hasError('required')">
    Password confirmation is required
  </md-error>

<md-error *ngIf="registerForm.controls['passwordGroup'].hasError('no-match')">
    Passwords don't match
  </md-error>

</md-form-field>

Cependant, l'erreur md régie par l'erreur "aucune correspondance" ne s'affiche jamais. Donc, pour déboguer ceci sur la page, j’ai ajouté ce qui suit:

no-match = {{registerForm.controls['passwordGroup'].hasError('no-match')}} 
invalid = {{registerForm.controls['passwordGroup'].invalid}}

Sans surprise, les lignes de débogage affichent la valeur true/false comme prévu. Cependant, "l'erreur-md" n'est jamais affichée ... sauf lorsque l'erreur "requis" est affichée. J'ai l'impression que le problème est dû au fait que [formControl] se réfère à passwordConfirmation FormControl, et donc sans que cela soit invalide, l'erreur md n'est pas affichée. Toutefois, j'aimerais que cette erreur s'affiche lorsque le groupe de formulaires externe n'est pas valide.

Tous les indicateurs sur lesquels je me trompe ici seraient vraiment utiles! 

Enfin, j’ai essayé d’autres moyens de contourner ce problème, par exemple en définissant l’erreur sur le PasswordConfirmation FormControl, qui fonctionne, mais j'aimerais savoir pourquoi ma mise en œuvre actuelle échoue.

6
user1615376

J'ai constaté que j'étais capable de résoudre exactement le même problème en utilisant un ErrorStateMatcher personnalisé (le contrôle par défaut exige que le contrôle soit dans un état non valide avant d'afficher les erreurs éventuelles)

export class ParentErrorStateMatcher implements ErrorStateMatcher {
    isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
        const isSubmitted = !!(form && form.submitted);
        const controlTouched = !!(control && (control.dirty || control.touched));
        const controlInvalid = !!(control && control.invalid);
        const parentInvalid = !!(control && control.parent && control.parent.invalid && (control.parent.dirty || control.parent.touched));

        return isSubmitted || (controlTouched && (controlInvalid || parentInvalid));
    }
}

Ceci est exposé en tant que variable sur mon composant de page comme si ...

@Component({
    templateUrl: 'register.page.component.html',
    styleUrls: ['register.page.styles.css']
})
export class RegisterPageComponent implements OnInit {
    registerForm: FormGroup;
    parentErrorStateMatcher = new ParentErrorStateMatcher();

    // Accessors
    get name() { return this.registerForm.get('name'); }
    get email() { return this.registerForm.get('email'); }
    get passwords() { return this.registerForm.get('passwords'); }
    get password() { return this.registerForm.get('passwords.password'); }
    get confirmPassword() { return this.registerForm.get('passwords.confirmPassword'); }
...

Avec ce formulaire ...

this.registerForm = this.formBuilder.group({
        name: ['', [
            Validators.required,
            Validators.maxLength(256)]
        ],
        email: ['', [
            Validators.email,
            Validators.required,
            Validators.maxLength(256)]
        ],
        passwords: this.formBuilder.group({
            password: ['', [
                Validators.required,
                Validators.maxLength(128)
            ]],
            confirmPassword: ['', [
                Validators.required
            ]]
        },
            {
                validator: CustomValidators.doNotMatch('password', 'confirmPassword')
            }),
    });

Et cette balise pour les champs de mot de passe (voir errorStateMatcher et la dernière erreur de mat pour le contrôle d'entrée confirmPassword) ...

<div formGroupName="passwords">
    <mat-form-field class="full-width">
        <input matInput placeholder="Password" type="password" name="password" id="password" formControlName="password" required/>
        <mat-error *ngIf="password.errors && password.errors.required">
            Please enter your password
        </mat-error>
        <mat-error *ngIf="password.errors && password.errors.maxLength">
            Password must be less than 128 characters long
        </mat-error>
    </mat-form-field>

    <mat-form-field class="full-width">
        <input matInput placeholder="Confirm Password" type="password" name="confirmPassword" id="confirmPassword" formControlName="confirmPassword" required
            [errorStateMatcher]="parentErrorStateMatcher"/>
        <mat-error *ngIf="confirmPassword.errors && confirmPassword.errors.required">
            Please confirm your password
        </mat-error>
        <mat-error *ngIf="passwords.errors && passwords.errors.doNotMatch">
            Passwords do not match
        </mat-error>
    </mat-form-field>
</div>

Je me sens plus marginalement plus déroutant que lorsque je n'utilisais pas Material, mais je suis content du compromis et le seul code supplémentaire est le matcher personnalisé :)

7
George Goodchild

J'ai déjà rencontré ce problème avec la validation personnalisée telle que },{validator: ValidatePasswordConfirmation} sur Angular Entrées de matériau, et il semble que ce soit le problème de Matériau. Voici mon numéro sur la page Angular _ Material: https://github.com/angular/material2/issues/7084 . Il n'a pas de réponse, donc le problème est toujours d'actualité. Ma décision était d'utiliser la validation après avoir soumis le formulaire, ce n'est pas très beau et correct, mais ça fonctionne. J'espère que le problème sera résolu dans la prochaine version.

Ou si vous voulez une validation en temps réel, vous pouvez faire quelque chose comme ceci:

checkArePasswordsValid() {
    this.passwordsAreEqual = this.passwordValue === this.passwordConfirmationValue ? true : false;
}

et utilisez cette valeur booléenne pour la validation.

0
Commercial Suicide

Ce code fonctionne parfaitement bien dans mon cas. La structure du code est la suivante:

// component.ts
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';

function validate(v: FormGroup): any {
  let a = k.get('a');
  let b = k.get('b');
  if (a.value != undefined && (b.value == undefined || a.value == "none" || b.value == " ")) {
    return { ab: true };
  }
  return null;
}

@Component({
})

export class component implements OnInit, OnDestroy {
  constructor(){}
  ngOnInit() {
    this.registerForm = formBuilder.group({
        'email':[null,Validators.compose([Validators.required, ValidateEmail])],
        'firstName':[null, Validators.required],
        'lastName':[null, Validators.required],
        'passwordGroup': formBuilder.group({
            'password':[null, Validators.compose([Validators.required,Validators.minLength(8)])],
            'passwordConfirmation':[null, Validators.required],
        },{validator: ValidatePasswordConfirmation})
    });
  }
}

L'affichage des messages d'erreur HTML si condition est utilisé de la même manière que celle mentionnée ci-dessus dans votre code.

0
Tejal