4

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);
  }
}
justinledouxweb
  • 1,337
  • 3
  • 13
  • 26
  • What issue are you having? Nothing displaying at all or no binding? Build or console errors? – NPhillips Oct 23 '16 at 21:12
  • The values of that last array item are displayed 5 times. No errors. What is weird is that if I change any of the input values, the right object in the array changes. – justinledouxweb Oct 23 '16 at 21:52
  • If I concatenate the input's name with the index, it works fine... but I'm not sure if it's the angular 2 way of not... – justinledouxweb Oct 23 '16 at 21:59

1 Answers1

13

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.