web-dev-qa-db-fra.com

Angular 2. Comment utiliser un tableau d'objets pour les contrôles dans les formulaires réactifs

J'ai besoin de créer dynamiquement textarea pour les formulaires. J'ai le modèle suivant:

this.fields = {
      isRequired: true,
      type: {
        options: [
          {
            label: 'Option 1',
            value: '1'
          },
          {
            label: 'Option 2',
            value: '2'
          }
        ]
      }
    };

Et forme:

this.userForm = this.fb.group({
      isRequired: [this.fields.isRequired, Validators.required],
      //... here a lot of other controls
      type: this.fb.group({
         options: this.fb.array(this.fields.type.options),
      })
});

Partie de modèle:

<div formGroupName="type">
       <div formArrayName="options">
         <div *ngFor="let option of userForm.controls.type.controls.options.controls; let i=index">
            <textarea [formControlName]="i"></textarea>
         </div>
      </div>
</div>

Donc, comme vous pouvez le voir, j'ai un tableau d'objets et je veux utiliser la propriété label pour l'afficher dans un textarea. Maintenant, je vois [object Object]. Si je change options en tableau de chaînes simple, comme: ['Option 1', 'Option 2'], alors tout fonctionne bien. Mais j'ai besoin d'utiliser des objets. Donc, au lieu de:

<textarea [formControlName]="i"></textarea>

J'ai essayé:

<textarea [formControlName]="option[i].label"></textarea>

Mais ça ne marche pas.
Comment utiliser un tableau d'objets?

C'est Plunkr

13
user348173

Vous devez ajouter un groupe de formulaires, qui contient votre label et votre value. Cela signifie également que l'objet créé par le formulaire a la même construction que votre objet fields.

ngOnInit() {
  // build form
  this.userForm = this.fb.group({
    type: this.fb.group({
      options: this.fb.array([]) // create empty form array   
    })
  });

  // patch the values from your object
  this.patch();
}

Après cela, nous corrigeons la valeur avec la méthode appelée dans votre OnInit:

patch() {
  const control = <FormArray>this.userForm.get('type.options');
  this.fields.type.options.forEach(x => {
    control.Push(this.patchValues(x.label, x.value))
  });
}

// assign the values
patchValues(label, value) {
  return this.fb.group({
    label: [label],
    value: [value]
  })    
}

Enfin, voici un

démo

22
AJT82

La réponse de AJT_82 m’a été si utile que j’ai pensé partager le même procédé de réutilisation du code et la construction d’un exemple similaire, qui pourrait avoir un cas d’utilisation plus courant, invitant plusieurs personnes à s’inscrire simultanément. Comme ça: form screenshot

Je pensais que cela pourrait aider les autres, c'est pourquoi je l'ajoute ici.

Vous pouvez voir que le formulaire est un simple tableau d'entrées de texte pour les courriels, avec un validateur personnalisé chargé sur chacun d'eux. Vous pouvez voir la structure JSON dans la capture d'écran - voir la ligne de pré dans le modèle (grâce à AJT), une idée très utile lors du développement pour voir si votre modèle et vos contrôles sont câblés!

Commençons par déclarer les objets dont nous avons besoin. Notez que 3 chaînes vides sont les données du modèle (que nous lierons aux entrées de texte):

  public form: FormGroup;
  private control: FormArray;
  private emailsModel = { emails: ['','','']} // the model, ready to hold the emails
  private fb : FormBuilder;

Le constructeur est propre (pour faciliter les tests, il suffit d’injecter à userService l’envoi des données du formulaire après soumission):

  constructor(
    private _userService: UserService,
  ) {}

Le formulaire est construit dans la méthode init, y compris le stockage d'une référence au contrôle emailsArray lui-même afin que nous puissions vérifier ultérieurement si ses enfants (les entrées réelles) sont touchés et, le cas échéant, ont-ils des erreurs:

  ngOnInit() {
    this.fb = new FormBuilder;
    this.form = this.fb.group({
      emailsArray: this.fb.array([])
    });
    this.control = <FormArray>this.form.controls['emailsArray'];
    this.patch();    
  }

  private patch(): void {
    // iterate the object model and extra values, binding them to the controls
    this.emailsModel.emails.forEach((item) => {
      this.control.Push(this.patchValues(item));
    })
  }

C'est ce qui construit chaque contrôle d'entrée (de type AbstracControl) avec le validateur:

  private patchValues(item): AbstractControl {
    return this.fb.group({
      email: [item, Validators.compose([emailValidator])] 
    })
  }

Les deux méthodes d'assistance pour vérifier si l'entrée a été touchée et si le validateur a généré une erreur (voir le modèle pour voir comment elles sont utilisées - remarque, je passe la valeur d'index du tableau à partir de *ngFor dans le modèle):

  private hasError(i):boolean {
    // const control = <FormArray>this.form.controls['emailsArray'];
    return this.control.controls[i].get('email').hasError('invalidEmail');
  }
  private isTouched(i):boolean {
    // const control = <FormArray>this.form.controls['emailsArray'];
    return this.control.controls[i].get('email').touched;
  }

Voici le validateur:

export function emailValidator(control: FormControl): { [key: string]: any } {
    var emailRegexp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/;
    if (control.value && !emailRegexp.test(control.value)) {
        return { invalidEmail: true };
    }
}

Et le modèle:

<form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" class="text-left">
    <div formArrayName="emailsArray">
        <div *ngFor="let child of form.controls.emailsArray.controls; let i=index">
            <div class="form-group" formGroupName="{{i}}">
                <input formControlName="email" 
                       class="form-control checking-field" 
                       placeholder="Email" type="text">
                <span class="help-block" *ngIf="isTouched(i)">
                    <span class="text-danger" 
                          *ngIf="hasError(i)">Invalid email address
                    </span>
                </span>
            </div>                   
        </div>
    </div>
    <pre>{{form.value | json }}</pre>            
    <div class="form-group text-center">
      <button class="btn btn-main btn-block" type="submit">INVITE</button>
    </div>
</form>

Pour ce que ça vaut, j'avais commencé avec cet horrible fouillis - mais si vous regardez le code ci-dessous, vous pourriez mieux comprendre le code ci-dessus!

  public form: FormGroup;
  public email1: AbstractControl;
  public email2: AbstractControl;
  public email3: AbstractControl;
  public email4: AbstractControl;
  public email5: AbstractControl;

  constructor(
    fb: FormBuilder
  ) { 
       this.form = fb.group({
      'email1': ['', Validators.compose([emailValidator])],
      'email2': ['', Validators.compose([emailValidator])],
      'email3': ['', Validators.compose([emailValidator])],
      'email4': ['', Validators.compose([emailValidator])],
      'email5': ['', Validators.compose([emailValidator])],
        });
    this.email1 = this.form.controls['email1'];
    this.email2 = this.form.controls['email2'];
    this.email3 = this.form.controls['email3'];
    this.email4 = this.form.controls['email4'];
    this.email5 = this.form.controls['email5'];
  }

et ce qui précède a utilisé 5 de ces divs dans le modèle - pas très sec!

<div class="form-group">
    <input [formControl]="email1" class="form-control checking-field" placeholder="Email" type="text"> 
    <span class="help-block" *ngIf="form.get('email1').touched">
        <span class="text-danger" *ngIf="form.get('email1').hasError('invalidEmail')">Invalid email address</span>
    </span> 
</div>
11
rmcsharry

Je suppose que ce n'est pas possible avec FormControlName.

Vous pouvez utiliser ngModel .. jetez un oeil à votre plunker modifié:

http://plnkr.co/edit/0DXSIUY22D6Qlvv0HF0D?p=preview

@Component({
  selector: 'my-app',
  template: `
    <hr>
    <form [formGroup]="userForm" (ngSubmit)="submit(userForm.value)">
     <input type="checkbox" formControlName="isRequired"> Required Field
    <div formGroupName="type">
       <div formArrayName="options">
         <div *ngFor="let option of userForm.controls.type.controls.options.controls; let i=index">
            <label>{{ option.value.label }}</label><br />

            <!-- change your textarea -->
            <textarea [name]="i" [(ngModel)]="option.value.value" [ngModelOptions]="{standalone: true}" ></textarea>
         </div>
      </div>
    </div>

    <button type="submit">Submit</button>
    </form>
    <br>
    <pre>{{userForm.value | json }}</pre>
  `,
})
export class App {
  name:string;
  userForm: FormGroup;
  fields:any;

  ngOnInit() {
    this.fields = {
      isRequired: true,
      type: {
        options: [
          {
            label: 'Option 1',
            value: '1'
          },
          {
            label: 'Option 2',
            value: '2'
          }
        ]
      }
    };

    this.userForm = this.fb.group({
      isRequired: [this.fields.isRequired, Validators.required],
      //... here a lot of other controls
      type: this.fb.group({
         // .. added map-function
         options: this.fb.array(this.fields.type.options.map(o => new FormControl(o))),
      })
    });
  }

  submit(value) {
    console.log(value);
  }

  constructor(private fb: FormBuilder) {  }

  addNumber() {
    const control = <FormArray>this.userForm.controls['numbers'];
    control.Push(new FormControl())
  }
}
0
mxii