Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 form with array of object inputs

I'm building an invoicing app to learn Angular2. The issue I am hitting is how to build the line item component where a line contains 3 inputs that should come from and bind to an object in an array of line item.

In angular 1, I can easily achieve this by adding an ng-form directive to the container of the inputs. What is the equivalent here?

Here is my code:

HTML:

<form name="form" ng-submit="$ctrl.submit(form)" novalidate>

<!-- some more code -->

<ul class="list invoice-table__body">
  <li *ngFor="let item of model.lineItems; let i = index">
    <input
      type="text"
      name="description"
      class="col-sm-8"
      [(ngModel)]="item.description">

    <input
      type="number"
      name="quantity"
      class="col-sm-2"
      [(ngModel)]="item.quantity">

    <input
      type="number"
      name="total"
      class="col-sm-2"
      [(ngModel)]="item.total">
  </li>
</ul>

<!-- some more code -->

</form>

Component:

import { Component } from '@angular/core';
import { Invoice } from './invoice.model';
import { InvoiceLineItems } from './invoice-line-item.model';

@Component({
  selector: 'create-invoice',
  templateUrl: 'create-invoice/create-invoice.html'
})
export class CreateInvoiceComponent {
  model: Invoice = new Invoice(
    85,
    'CAD',
    null,
    [ // lineItems
      new InvoiceLineItems('Web development for BAnQ'),
      new InvoiceLineItems('Sept 1 to 3', 14, 910),
      new InvoiceLineItems('Sept 5 to 10', 34, 5293),
      new InvoiceLineItems('Sept 11 to 20', 24, 5293),
      new InvoiceLineItems('Sept 21 to 38', 11, 2493),
    ],
    13989,
    100,
    200,
    15000,
    '',
    null,
    '$'
  );

  getTotal(): number {
    return this.model.lineItems.reduce(
      (a, b): number => a + (isNaN(b.total) ? 0 : b.total),
      0);
  }
}
like image 578
justinledouxweb Avatar asked Dec 08 '22 20:12

justinledouxweb


1 Answers

The problem here is in the input name, in your case you are using name = "description", what is happening here is that is always is going to upadte the form.description.value with the last description. In your case you have array of descriptions, you need to have array of form.description[i].value

So the fix is change description to be unique.

name="description_{{i}}"

Repeat this for every input inside a ngFor. For fixing this issue you can do this:

<ul class="list invoice-table__body">
  <li *ngFor="let item of invoice.lineItems; let i = index">

    <input
      type="text"
      name="description_{{i}}"
      class="col-sm-8"
      [(ngModel)]="invoice.lineItems[i].description">

    <input
      type="number"
      name="quantity_{{i}}"
      class="col-sm-2"
      [(ngModel)]="invoice.lineItems[i].quantity">

    <input
      type="number"
      name="total_{{i}}"
      class="col-sm-2"
      [(ngModel)]="invoice.lineItems[i].total">
  </li>
</ul>

you can see your example working here: https://plnkr.co/edit/orldGCSYDUtlxzMFsVjL?p=preview

My recommendation is always works with ReactiveForms (Model Driven Forms), maybe the only case i will use FormsModule (Template Driven Forms) is for small form. Its Easier and its better for doing arrays of data.

Hope its solved your issue.

like image 139
Daniel Dancziger Avatar answered Dec 10 '22 11:12

Daniel Dancziger