web-dev-qa-db-fra.com

Angular 2 - utiliser ngControl avec des champs optionnels

J'ai du mal à utiliser à la fois * ngIf dans un formulaire et ngFormModel pour valider ledit formulaire.

Le cas d'utilisation est le suivant: en fonction des entrées de l'utilisateur, masquez ou désactivez certains champs spécifiques du formulaire. Mais si ces entrées sont affichées, elles doivent être validées.

Lorsque seule la validation de base est requise, je peux effectuer d'une manière ou d'une autre:

  • Si l'entrée est seulement requise, cela fonctionne A-OK en utilisant ngControl et required
  • Si un format spécifique est nécessaire, il est possible d'utiliser l'attribut pattern. Ce n'est pas angulaire, mais ça marche.

Mais pour implémenter des validations plus complexes, j'ai essayé d'utiliser ngControl couplé à ngFormModel afin d'utiliser des contrôles personnalisés. J'ai utilisé des morceaux de code trouvés dans les pages suivantes:

Comment ajouter un modèle de validation de formulaire dans angular2 (et les liens qui y sont référencés)

Angular2 Forms: Validations, ngControl, ngModel etc.

Mon code est le suivant:

HTML

<div>
  <h1>Rundown of the problem</h1>
  <form (ngSubmit)="submitForm()" #formState="ngForm" [ngFormModel]="myForm">
    <div class="checkbox">
      <label>
        <input type="checkbox" [(ngModel)]="model.hideField" ngControl="hideField"> Is the input below useless for you ?
      </label>
    </div>

    <div *ngIf="!model.hideField">
      <div class="form-group">
        <label for="optionalField">Potentially irrelevant field </label>
        <input type="text" class="form-control" [(ngModel)]="model.optionalField" ngControl="optionalField" required #optionalField="ngForm">
        <div [hidden]="optionalField.valid || optionalField.pristine" class="alert alert-warning">
          This input must go through myCustomValidator(), so behave.
        </div>
      </div>
    </div>

    <button type="submit" class="btn btn-primary" [disabled]="!formState.form.valid">I can't be enabled without accessing the input :(</button>
    <button type="submit" class="btn btn-default">Submit without using form.valid (boo !)</button>
  </form>
</div>

TypeScript

import {Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core';
import {NgForm, FormBuilder, Validators, ControlGroup, FORM_DIRECTIVES}    from 'angular2/common';

@Component({
  selector: 'accueil',
  templateUrl: 'app/accueil.component.bak.html',
  directives:[FORM_DIRECTIVES],
  providers: [FormBuilder]
})
export class AccueilComponent implements AfterViewInit {

  private myForm: ControlGroup;
  model: any;

  constructor(fb: FormBuilder, cdr: ChangeDetectorRef) {
    this.cdr = cdr ;

    this.model = {} ;
    this.myForm = fb.group({
      "hideField": [false],
      "optionalField": [this.model.optionalField, Validators.compose([this.myCustomValidator])]
    });
  }

  ngAfterViewInit() {
    // Without this, I get the "Expression has changed after it was checked" exception.
    // See also : https://stackoverflow.com/questions/34364880/expression-has-changed-after-it-was-checked
    this.cdr.detectChanges();
  }

  submitForm(){
    alert("Submitted !");
  }

  myCustomValidator(optionalField){
    // Replace "true" by "_someService.someCheckRequiringLogicOrData()"
    if(true) {
      return null;
    }

    return { "ohNoes": true };
  }
}

Même si l'entrée est supprimée du modèle avec * ngIf, le constructeur fait toujours référence au contrôle. Ce qui à mon tour m'empêche d'utiliser [disabled] = "! FormState.form.valid", car myForm est naturellement INVALID.

Est-ce que je vise autant que possible en utilisant Angular 2? Je suis sûr que ce n'est pas si rare qu'un cas d'utilisation, mais encore une fois, avec mes connaissances actuelles, je ne vois pas comment je pourrais le faire fonctionner.

Merci !

12
phl

Ce que vous pouvez essayer de faire est de réinitialiser les validateurs sur votre contrôle. Ce qui signifie que si vous souhaitez qu'un nouvel ensemble de validateurs soit lié à un contrôle en raison d'un changement d'état, vous devez redéfinir la fonction de validation.

Dans votre cas, lorsque votre case à cocher est cochée/décochée, vous souhaitez que les événements suivants se produisent:

  1. Définissez l'entrée comme facultative (non requise), mais validez-la par rapport à votre validateur personnalisé.
  2. Rétablir l'état initial des validateurs du contrôle lorsque la case à cocher est décochée.
  3. Validez à nouveau le contrôle pour que form.valid soit mis à jour.

Voir mon exemple de plnkr basé sur le guide des formulaires d'Angular.io

if (optional)
   this.heroFormModel.controls['name'].validator = Validators.minLength(3);
else
   this.heroFormModel.controls['name'].validator =
        Validators.compose([Validators.minLength(3), Validators.required]);

this.heroFormModel.controls['name'].updateValueAndValidity();
8
Alex

Je viens de rencontrer exactement le même problème, et j'ai trouvé une solution de contournement qui repose sur incluant et excluant manuellement les contrôles:

import {Directive, Host, SkipSelf, OnDestroy, Input, OnInit} from 'angular2/core';
import {ControlContainer} from 'angular2/common';

@Directive({
  selector: '[ngControl]'
})
export class MyControl implements OnInit, OnDestroy {
  @Input() ngControl:string;
  constructor(@Host() @SkipSelf() private _parent:ControlContainer) {}

  ngOnInit():void {
    // see https://github.com/angular/angular/issues/6005
    setTimeout(() => this.formDirective.form.include(this.ngControl));
  }

  ngOnDestroy():void {
    this.formDirective.form.exclude(this.ngControl);
  }

  get formDirective():any {
    return this._parent.formDirective;
  }
}

Pour que cela fonctionne, tous les contrôles dynamiques doivent d'abord être exclus du formulaire; voir le plunkr pour plus de détails.

2
Christian Zosel

Voici une version mise à jour pour RC4. Je l'ai également renommé npControl pour mes besoins.

import { Directive, Host, OnDestroy, Input, OnInit } from '@angular/core';
import { ControlContainer } from '@angular/forms';

@Directive({
  selector: '[npControl]'
})
export class NPControlDirective implements OnInit, OnDestroy {
  @Input() npControl: string;

  constructor(@Host() private _parent: ControlContainer
  ) { }

  ngOnInit(): void {
    console.log('include ', this.npControl);
    setTimeout(() => this.formDirective.form.include(this.npControl));
  }

  ngOnDestroy(): void {
    console.log('exclude ', this.npControl);
    this.formDirective.form.exclude(this.npControl);
  }

  get formDirective(): any {
    return this._parent;
  }
}
0
Jesse Sanders