web-dev-qa-db-fra.com

Angular Formulaires réactifs avec tableaux de formulaires imbriqués

Je suis nouveau sur Angular 2 et j'ai décidé que la meilleure façon d'apprendre serait de passer par les guides officiels Angular.

J'ai parcouru le Guide des formulaires réactifs https://angular.io/guide/reactive-forms

lien de démonstration: https://stackblitz.com/angular/jammvmbrpxle

Bien que le contenu soit globalement bon, je ne sais pas comment mettre en œuvre un formulaire plus complexe. Dans l'exemple donné, chaque héros a le potentiel pour de nombreuses adresses. Une adresse elle-même est un objet plat.

Que se passe-t-il si Adresses contenait des informations supplémentaires telles que la couleur et le type de pièces situées à l'adresse?.

export class Address {
    street = '';
    city   = '';
    state  = '';
    Zip    = '';
    rooms = Room[];
}

export class Room {
     type = '';
}

afin que le modèle de formulaire ressemble à ceci ...

createForm() {
this.heroForm = this.fb.group({
  name: '',
  secretLairs: this.fb.array([
      this.fb.group({
          street: '',
          city: '',
          state: '',
          Zip: '',
          rooms: this.fb.array([
              this.fb.group({
                 type: ''
          })]),
      })]),
  power: '',
  sidekick: ''
});

}

EDIT - Code finalisé qui fonctionne avec ngOnChanges

hero-detail.component.ts

createForm() {
    this.heroForm = this.fb.group({
      name: '',
      secretLairs: this.fb.array([
        this.fb.group({
          street: '',
          city: '',
          state: '',
          Zip: '',
          rooms: this.fb.array([
            this.fb.group({
              type: ''
            })
          ])
        })
      ]),
      power: '',
      sidekick: ''
    });
  }

  ngOnChanges() {
    this.heroForm.reset({
      name: this.hero.name,
    });
    this.setAddresses(this.hero.addresses);
  }

  setAddresses(addresses: Address[]) {
    let control = this.fb.array([]);
    addresses.forEach(x => {
      control.Push(this.fb.group({
        street: x.street,
        city: x.city,
        state: x.state,
        Zip: x.Zip,
        rooms: this.setRooms(x) }))
    })
    this.heroForm.setControl('secretLairs', control);
  }

  setRooms(x) {
    let arr = new FormArray([])
    x.rooms.forEach(y => {
      arr.Push(this.fb.group({ 
        type: y.type 
      }))
    })
    return arr;
  }

hero-detail.component.html (la partie du tableau de formulaire imbriqué)

<div formArrayName="secretLairs" class="well well-lg">
  <div *ngFor="let address of heroForm.get('secretLairs').controls; let i=index" [formGroupName]="i" >
    <!-- The repeated address template -->
    <h4>Address #{{i + 1}}</h4>
    <div style="margin-left: 1em;">
      <div class="form-group">
        <label class="center-block">Street:
          <input class="form-control" formControlName="street">
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">City:
          <input class="form-control" formControlName="city">
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">State:
          <select class="form-control" formControlName="state">
            <option *ngFor="let state of states" [value]="state">{{state}}</option>
          </select>
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">Zip Code:
          <input class="form-control" formControlName="Zip">
        </label>
      </div>
    </div>
    <br>
    <!-- End of the repeated address template -->
    <div formArrayName="rooms" class="well well-lg">
      <div *ngFor="let room of address.get('rooms').controls; let j=index" [formGroupName]="j" >
          <h4>Room #{{j + 1}}</h4>
          <div class="form-group">
            <label class="center-block">Type:
              <input class="form-control" formControlName="type">
            </label>
          </div>
      </div>
    </div>
  </div>
  <button (click)="addLair()" type="button">Add a Secret Lair</button>
</div>
27
JR90

Ce n'est pas très différent d'avoir un formarray imbriqué. En gros, vous venez de dupliquer le code que vous avez ... avec un tableau imbriqué :) Alors voici un exemple:

myForm: FormGroup;

constructor(private fb: FormBuilder) {
  this.myForm = this.fb.group({
    // you can also set initial formgroup inside if you like
    companies: this.fb.array([])
  })
}

addNewCompany() {
  let control = <FormArray>this.myForm.controls.companies;
  control.Push(
    this.fb.group({
      company: [''],
      // nested form array, you could also add a form group initially
      projects: this.fb.array([])
    })
  )
}

deleteCompany(index) {
  let control = <FormArray>this.myForm.controls.companies;
  control.removeAt(index)
}

Il s'agit donc de l'ajout et de la suppression pour le tableau de formulaire le plus à l'extérieur. L'ajout et la suppression de formgroups au tableau de formulaire imbriqué ne font que dupliquer le code. Où du modèle nous passons le formgroup actuel à quel tableau vous voulez ajouter (dans ce cas) un nouveau projet/supprimer un projet.

addNewProject(control) {
  control.Push(
    this.fb.group({
      projectName: ['']
  }))
}

deleteProject(control, index) {
  control.removeAt(index)
}

Et le modèle de la même manière, vous parcourez votre formarray externe, puis à l'intérieur de celui-ci, votre tableau de formulaires interne:

<form [formGroup]="myForm">
  <div formArrayName="companies">
    <div *ngFor="let comp of myForm.get('companies').controls; let i=index">
    <h3>COMPANY {{i+1}}: </h3>
    <div [formGroupName]="i">
      <input formControlName="company" />
      <button (click)="deleteCompany(i)">
         Delete Company
      </button>
      <div formArrayName="projects">
        <div *ngFor="let project of comp.get('projects').controls; let j=index">
          <h4>PROJECT {{j+1}}</h4>
          <div [formGroupName]="j">
            <input formControlName="projectName" />
            <button (click)="deleteProject(comp.controls.projects, j)">
              Delete Project
            </button>
          </div>
        </div>
        <button (click)="addNewProject(comp.controls.projects)">
          Add new Project
        </button>
      </div>
    </div>
  </div>
</div>

DÉMO

MODIFIER:

Pour définir les valeurs de votre formulaire une fois que vous avez des données, vous pouvez appeler les méthodes suivantes pour itérer vos données et définir les valeurs de votre formulaire. Dans ce cas, data ressemble à ceci:

data = {
  companies: [
    {
      company: "example comany",
      projects: [
        {
          projectName: "example project",
        }
      ]
    }
  ]
}

Nous appelons setCompanies pour définir les valeurs de notre forme:

setCompanies() {
  let control = <FormArray>this.myForm.controls.companies;
  this.data.companies.forEach(x => {
    control.Push(this.fb.group({ 
      company: x.company, 
      projects: this.setProjects(x) }))
  })
}

setProjects(x) {
  let arr = new FormArray([])
  x.projects.forEach(y => {
    arr.Push(this.fb.group({ 
      projectName: y.projectName 
    }))
  })
  return arr;
}
64
AJT82