Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 Dynamic Nested Form

Basically I want to create a dynamic form with nested objects like the picture below: Angular 2 Nested Form Example

  • Pay offs are in an array on the model
  • We should be able to add/remove pay offs as needed.
  • The form should sync underlying form controls and model
  • The number of pay offs is arbitrary and should be loaded into the form from the model

There are no working examples that I could find as how to do this in Angular 2, although this was really easy to do in Angular 1.


Below is my original question, I've since updated it for clarification (see above):

First I just wanted to point out that I'm aware that a new version of Angular 2 rc.2 has just been released a few days ago. So the code for creating a dynamic, nested form may have changed some but there's not enough documentation to figure this out.

In the latest version(s) of Angular 2 (I'm currently using rc.1 but planning to update to rc.2) I need to create a form like this (pseudo-code of view):

<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
  <input type="text" ngControl="name">

  <div *ngFor="let expense for expenses; let i = index;" control-group="expenses">
    <input type="text" ngControl="expense.amount" [(ngModel)]="myModel.expenses[i].amount"> 
    <input type="checkbox" ngControl="expense.final" [(ngModel)]="myModel.expenses[i].final"> 
  </div>

  <a class="button" (click)="addExpenseControl()">Add</a>
  <a class="button" (click)="deleteExpenseControl()">Delete</a>
</form>

So the pseudo-code above won't work but to be honest because of lack of documentation I can't figure out how to wire something like this up. There's a few tutorials about nested ControlGroup but this won't fit the case here since we need to be able to dynamically add and remove control groups, and also I need to be able to sync them with a model.

I found this plunkr here provided by Angular team which allows adding of Controls to a form--but this is not adding/removing a ControlGroup, rather it's using ControlArray and I'm not sure if that applies here?

I'm very familiar with using the newer model-based Angular 2 forms however I'm grasping for resources in order to properly nest them (dynamically!), and tie this nested data into the main form model. How would I refer to nested controls in the view? Is the pseudo-code above even close? I'd post code from my controller but honestly I wouldn't know where to start when it comes to the nested expenses (ControlGroup ??) above...

like image 552
FireDragon Avatar asked Jun 21 '16 21:06

FireDragon


People also ask

What is FormArray in Angular?

A FormArray aggregates the values of each child FormControl into an array. It calculates its status by reducing the status values of its children. For example, if one of the controls in a FormArray is invalid, the entire array becomes invalid.

Can we have form inside form in Angular?

In simple words, nested forms are forms within a form. Using nested forms, we can create an array of objects within a single field and we can have an array of these fields. Hence, the nested form helps us manage large form groups and divides it into small groups.


Video Answer


1 Answers

I had to figure this out on my own because it seems that forms are still changing in Angular 2 and I've not seen any other examples similar to this (although it seems like a very common use-case).

Here is a plunkr of working example using Angular2 RC3.

I am using updated Angular 2 form code from this document.

app.component.ts (contains the form):

import { Component } from '@angular/core';
import {REACTIVE_FORM_DIRECTIVES, FormControl, FormGroup, FormArray} from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: 'app/app.html',
  directives: [REACTIVE_FORM_DIRECTIVES],
  providers:  []
})
export class AppComponent {
  form: FormGroup;
  myModel:any;

  constructor() {
    // initializing a model for the form to keep in sync with. 
    // usually you'd grab this from a backend API
    this.myModel = {
      name: "Joanna Jedrzejczyk",
      payOffs: [
        {amount: 111.11, date: "Jan 1, 2016", final: false},
        {amount: 222.22, date: "Jan 2, 2016", final: true}
        ]
    }

    // initialize form with empty FormArray for payOffs
    this.form = new FormGroup({
      name: new FormControl(''),
      payOffs: new FormArray([])
    });

    // now we manually use the model and push a FormGroup into the form's FormArray for each PayOff
    this.myModel.payOffs.forEach( 
      (po) => 
        this.form.controls.payOffs.push(this.createPayOffFormGroup(po))
    );
  }

  createPayOffFormGroup(payOffObj) {
    console.log("payOffObj", payOffObj);
    return new FormGroup({
      amount: new FormControl(payOffObj.amount),
      date: new FormControl(payOffObj.date),
      final: new FormControl(payOffObj.final)
    });
  }

  addPayOff(event) {
    event.preventDefault(); // ensure this button doesn't try to submit the form
    var emptyPayOff = {amount: null, date: null, final: false};

    // add pay off to both the model and to form controls because I don't think Angular has any way to do this automagically yet
    this.myModel.payOffs.push(emptyPayOff);
    this.form.controls.payOffs.push(this.createPayOffFormGroup(emptyPayOff));
    console.log("Added New Pay Off", this.form.controls.payOffs)
  }

  deletePayOff(index:number) {
    // delete payoff from both the model and the FormArray
    this.myModel.payOffs.splice(index, 1);
    this.form.controls.payOffs.removeAt(index);
  }
}

Notice above that I manually push new FormGroup objects into the form.controls.payOffs array, which is a FormArray object.

app.html (contains form html):

  <form (ngSubmit)="onSubmit()" [formGroup]="form">

    <label>Name</label>
    <input type="text" formControlName="name" [(ngModel)]="myModel.name" placeholder="Name">

    <p>Pay Offs</p>
    <table class="simple-table">
      <tr>
        <th>Amount</th>
        <th>Date</th>
        <th>Final?</th>
        <th></th>
      </tr>
    <tbody>
      <tr *ngFor="let po of form.find('payOffs').controls; let i = index">
        <td>
          <input type="text" size=10 [formControl]="po.controls.amount" [(ngModel)]="myModel.payOffs[i].amount">
        </td>
        <td>
          <input type="text" [formControl]="po.controls.date" [(ngModel)]="myModel.payOffs[i].date">
        </td>
        <td>
          <input type="checkbox" [formControl]="po.controls.final" [(ngModel)]="myModel.payOffs[i].final">
        </td>
        <td>
          <button (click)="deletePayOff(i)" style="color: white; background: rgba(255, 0, 0, .5)">x</button>
        </td>
      </tr>
    </tbody>
    <tr>
      <td colspan="4" style="text-align: center; padding: .5em;">
        <button (click)="addPayOff($event)" style="color: white; background: rgba(0, 150, 0, 1)">Add Pay Off</button>
      </td>
    </tr>
    </table>

  </form>

In the html form I link the form to the model on the inputs with statements like so:

... [formControl]="po.controls.amount" [(ngModel)]="myModel.payOffs[i].amount" ...
like image 159
FireDragon Avatar answered Sep 28 '22 12:09

FireDragon