web-dev-qa-db-fra.com

Angular 2 - Messages de validation de forme réactive

Mon objectif est de mettre tous mes messages de validation dans le composant au lieu du fichier html

J'ai une page d'inscription et voici les champs:

public buildRegisterForm() {
this.userForm = this.fb.group({
  firstName: ['', [Validators.required, Validators.minLength(3)]],
  lastName: ['', [Validators.required, Validators.maxLength(50)]],
  emailGroup: this.fb.group({
    email: ['', [Validators.required, Validators.pattern(this.emailPattern)]],
    retypeEmail: ['', Validators.required],
  }, { validator: formMatcherValidator('email', 'retypeEmail') }),
  passwordGroup: this.fb.group({
    password: ['', [Validators.required, strongPasswordValidator()]],
    retypePassword: ['', Validators.required],
  }, { validator: formMatcherValidator('password', 'retypePassword')}),
});
}

Je suis ce tutoriel link pour réaliser ce que je veux qui est de mettre tous mes messages de validation dans un fichier composant au lieu d'un fichier html.

export const validationMessages = {
'firstName': {
'required': 'Your first name is required.',
'minlength': 'Your first name must be at least 3 characters long.'
},
'lastName': {
'required': 'Your last name is required.',
'minlength': 'Your last name must be less than 50 characters long.'
},
'emailGroup': {
  'email': {
      'required': 'Your email is required',
      'pattern': 'Your login email does not seem to be a valid email address.'
     },
 'retypeEmail': {
      'required': 'Your retype email is required',
      'match': 'The email provided do not match.'
   },
},
'passwordGroup':{
     'password': {
         'required': 'Your password is required',
         'strongPassword': 'Your password must be between 8 - 15 characters and must contain at least three of the following: upper case letter, lower case letter, number, symbol.'
     },
   'retypePassword': {
       'required': 'Your retype password is required',
        'match': 'The password provided do not match.'
  }
}


méthode onValueChanged

private onValueChanged(data?: any) {
if (!this.userForm) { return; }
const form = this.userForm;

// tslint:disable-next-line:forin
for (const field in this.formErrors) {
  // clear previous error message (if any)
  this.formErrors[field] = '';
  let control = form.get(field);
  // console.log("control", control.dirty);

  console.log("controlEmail", control);
  if (control && (control.dirty || control.touched) && control.invalid) {
    let messages = validationMessages[field];
    // tslint:disable-next-line:forin
    for (const key in control.errors) {
      this.formErrors[field] += messages[key] + ' ';
    }
  }
 }
}

Et cette méthode ne fonctionne pas lorsque j'ai un groupe multi formBuider ou un objet imbriqué. Des conseils pour ce 1?
similaire à ceci Comment valider des formulaires réactifs avec des groupes de formulaires imbriqués?

10
weikian

À mon avis, vous devez créer une boucle imbriquée à l'intérieur de la méthode onValueChanged(data)-. Étant donné que vous avez un grand nombre de groupes imbriqués, je ne vais pas reproduire cela. Mais la boucle imbriquée est générique, donc elle fonctionne pour tous vos groupes. Mais voici un exemple avec un seul groupe imbriqué au lieu de plusieurs. J'utilise l'exemple des héros.

Le nom du groupe imbriqué est group, et le contrôle de formulaire à l'intérieur qui est appelé child.

formErrors qui sont utilisés dans le code devrait donc avoir le child dans un group intérieur:

formErrors = {
  'name': '',
  'power': '',
  'group':{
    'child': ''
  }
};

Par conséquent, vous devez vous rappeler lorsque vous ajoutez la validation dans le modèle, vous devez utiliser:

<div *ngIf="formErrors.group.child">
   {{ formErrors.group.child }}
</div>

Les messages de validation ne seront pas dans group, mais tout comme les autres messages de validation:

validationMessages = {
  'name': {
    'required': 'Name is required.',
  },
  'power': {
    'required': 'Power is required.'
  },
  'child': {
    'required': 'Child is required.',
  }
};

Enfin, le onValueChanges modifié:

onValueChanged(data?: any) {
  if (!this.heroForm) { return; }
  const form = this.heroForm;

  // iterate toplevel of formErrors
  for (const field in this.formErrors) {
    // check if the field corresponds a formgroup (controls is present)
    if(form.get(field).controls ) {
      // if yes, iterate the inner formfields
      for(const subfield in form.get(field).controls) {
        // in this example corresponds = "child", reset the error messages
        this.formErrors[field][subfield] = '';
        // now access the actual formfield
        const control = form.get(field).controls[subfield];
        // validate and show appropriate error message
        if (control && control.dirty && !control.valid) {
          const messages = this.validationMessages[subfield];
          for (const key in control.errors) {
            this.formErrors[field][subfield] += messages[key] + ' ';
          }
        }
      }
    } 
    // does not contain a nested formgroup, so just iterate like before making changes to this method
    else {
      const control = form.get(field);
      this.formErrors[field] = '';
      if (control && control.dirty && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          this.formErrors[field] += messages[key] + ' ';
        }
      } 
    }
  }
}

Enfin, une DEMO :)

Plunker

Vous devez cependant vous rappeler que dans votre cas, cela fonctionne, mais s'il y avait des groupes imbriqués à l'intérieur des groupes imbriqués, cela ne fonctionnerait pas, alors vous devriez faire encore une autre boucle dans le onValueChanges, mais vous n'avez pas ce problème ici;)

8
AJT82

Vous pouvez également utiliser ce qui suit à côté de la méthode onValueChanged d'origine:

formErrors = {
  'name': '',
  'power': '',
  'group.child':''
};

validationMessages = {
  'name': {
    'required': 'Name is required.',
  },
  'power': {
    'required': 'Power is required.'
  },
  'group.child': {
    'required': 'Child is required.',
  }
};

onValueChanged(data?: any) {
    if (!this.heroForm) { return; }
    const form = this.heroForm;

    for (const field in this.formErrors) {
      // clear previous error message (if any)
      this.formErrors[field] = '';
      const control = form.get(field);

      if (control && control.dirty && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          this.formErrors[field] += messages[key] + ' ';
        }
      }
    }
  }
3
me98278

Je souhaite que la solution de Nehal fonctionne, mais je n'ai pas pu y arriver. Je suis venu avec ma solution après avoir un peu travaillé avec le code @ AJT_82. Mon besoin venait vraiment de vouloir vérifier les mots de passe en groupe et sa solution ne couvrait pas cela. J'ai donc inclus les autres pièces que j'ai utilisées pour créer la solution complète qui a fonctionné pour moi.

Je suis tombé sur ce problème en essayant de faire la validation de la confirmation du mot de passe dans angular 4 après avoir découvert que la méthode que j'avais utilisée dans 2 ne fonctionnait plus de la même façon. Les informations sur angular.io n'avaient pas vraiment aider beaucoup pour cela car une grande partie de l'information est fragmentée dans différents domaines de leur documentation.

Donc, pour clarifier, cela suit la méthode de forme réactive d'Angular. J'ai écrit ceci pour valider les mots de passe dans un cas où j'avais également d'autres restrictions de validation (le mot de passe doit comprendre entre 4 et 24 caractères, est requis, etc.). Cette méthode devrait tout aussi bien fonctionner pour la confirmation par e-mail avec quelques petits ajustements.

Premièrement, afin de comparer les validateurs d'un groupe, le formulaire html doit avoir un sous-groupe identifié à l'aide de l'identifiant formGroupName = "". Il s'agit de l'identifiant principal [formGroup]. Les entrées comparées doivent être à l'intérieur de cet élément étiqueté formGroupName. Dans mon cas, c'est juste un div.

<div class="title">Hero Registration Form</div>
<form [formGroup]="regForm" id="regForm" (ngSubmit)="onSubmit()">
    <div class="input">
        <label for="heroname">Heroname</label> <input type="text"
            id="heroname" class="form-control" formControlName="heroname"
            required />
        <div *ngIf="formErrors.heroname" class="hero-reg-alert">{{
            formErrors.heroname }}</div>
    </div>
    <div class="input">
        <label for="email">Email</label> <input type="email" id="email"
            class="form-control" formControlName="email" required />
        <div *ngIf="formErrors.email" class="hero-reg-alert">{{
            formErrors.email }}</div>
    </div>
    <div formGroupName="password">
        <div class="input">
            <label for="password1">Password</label> <input type="password"
                id="password1" class="form-control" formControlName="password1"
                required />
            <div *ngIf="formErrors.password.password1" class="hero-reg-alert">
                {{ formErrors.password.password1 }}</div>
        </div>
        <div class="input">
            <label for="password2">Re-Enter Password</label> <input
                type="password" id="password2" class="form-control"
                formControlName="password2" required />
            <div
                *ngIf="formErrors.password.password2 || formErrors.password.password"
                class="hero-reg-alert">{{ formErrors.password.password }} {{
                formErrors.password.password2 }}</div>
        </div>
    </div>
    <button type="submit" [disabled]="!regForm.valid">
        <span id="right-btntxt">SUBMIT</span>
    </button>
</form>

Vous remarquerez peut-être que j'ai des sous-valeurs pour formErrors comme

formErrors.password.password
formErrors.password.password1
formErrors.password.password2

Ceux-ci sont construits dans mon code comme si ...

  formErrors = {
    'username': '',
    'email': '',
    'password': {
      'password': '',
      'password1': '',
      'password2': ''
    }
  };

Je l'ai construit de cette façon car 'password1' et 'password2' contiennent mes formErrors pour chaque champ, tandis que 'password' contient mon erreur dans le cas d'une incompatibilité (où les deux mots de passe entrés ne sont pas égaux).

Voici la formule de onValueChanged (). Il vérifie si un champ est une instance de FormGroup. Si tel est le cas, il vérifie d'abord la validation personnalisée pour ce groupe de champs en premier (en les stockant dans 'this.formErrors [champ] [champ]'), puis procède à la gestion des validations de sous-champ. Dans le cas où il ne s'agit pas d'une instance de FieldGroup, la validation est gérée conformément à l'exemple du document d'orientation angular.io.

  onValueChanged(data?: any) {
    if (!this.regForm) {return;}
    const form = this.regForm;

    for (const field in this.formErrors) {
      const formControl = form.get(field);
      if (formControl instanceof FormGroup) {
        this.formErrors[field][field] = '';
        // check for custom validation on field group
        const control = form.get(field);
        // handle validation for field group
        if (control && control.dirty && !control.valid) {
          const messages = this.validationMessages[field];
          for (const key in control.errors) {
            this.formErrors[field][field] += messages[key] + ' ';
          }
        }
        // handle validation for subfields
        for (const subfield in formControl.controls) {
          console.log('SUBFIELD', subfield);
          this.formErrors[field][subfield] = '';
          const control = formControl.controls[subfield];
          if (control && control.dirty && !control.valid) {
            const messages = this.validationMessages[subfield];
            for (const key in control.errors) {
              this.formErrors[field][subfield] += messages[key] + ' ';
            }
          }
        }
      } 
        // alternate validation handling for fields without subfields (AKA not in a group)
      else {
        const control = form.get(field);
        this.formErrors[field] = '';
        if (control && control.dirty && !control.valid) {
          const messages = this.validationMessages[field];
          for (const key in control.errors) {
            this.formErrors[field] += messages[key] + ' ';
          }
        }
      }
    }
  }

En donnant au FieldGroup un sous-champ avec son propre nom, nous pouvons stocker les validations sur le FieldGroup. Tenter de le faire avec le code onValueChange normal écrase les sous-champs sur la ligne ...

    this.formErrors[field] = '';

Ne pas fournir un emplacement pour stocker la validation FieldGroup remplace les sous-champs ou ne gère pas le FieldGroup.

Si vous en avez besoin, voici comment le formulaire est construit à l'aide de buildFomr ();

 buildForm(): void {
    this.regForm = this.formBuilder.group({
      'username': [this.registerUser.username, [
        Validators.required,
        Validators.minLength(4),
        Validators.maxLength(24)
      ]
      ],
      'email': [this.registerUser.email, [
        Validators.email,
        Validators.required
      ]
      ],
      'password': this.formBuilder.group({
        'password1': [this.registerUser.password, [
          Validators.required,
          Validators.minLength(8),
          Validators.maxLength(24)
        ]
        ],
        'password2': ['', [
          Validators.required
        ]
        ]
      }, {validator: this.passwordMatchValidator})
    }); // end buildForm

    this.regForm.valueChanges
      .subscribe(data => this.onValueChanged(data));

    this.onValueChanged(); // (re)set validation messages now
  }

et c'est la fonction de validation personnalisée ...

  passwordMatchValidator(control: FormGroup): {[key: string]: any} {
    return control.get('password1').value !== control.get('password2').value ? {mismatch: true} : null;
  }
1
Jonathan Borgia