1

I'm building my own invoicing app to learn Angular 2. I created my model with formBuilder. My model contains an array of inputs (the line items of the invoice), and once that is mapped in my template, I am getting an error. (I followed this example: https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2).

Here is the error, the controller and the template:

Error:

Error in create-invoice/create-invoice.html:109:19 caused by: Cannot find control with name: 'i'

Controller:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, Validators, FormGroup, FormArray } from '@angular/forms';
import { Subscription } from 'rxjs/Rx';

import { Client } from '../clients/client.model';
import { Invoice, InvoiceLineItems } from '../invoices/invoice.model';
import { CreateInvoiceService } from './create-invoice.service';

@Component({
  selector: 'create-invoice',
  templateUrl: 'create-invoice/create-invoice.html',
  providers: [CreateInvoiceService]
})
export class CreateInvoiceComponent implements OnInit, OnDestroy {
  private FLOAT_REGEX: string = '^[0-9]+([,.][0-9]+)?$';
  public model: FormGroup;

  constructor(
    private _router: Router,
    private _route: ActivatedRoute,
    private _createInvoiceService: CreateInvoiceService,
    private _formBuilder: FormBuilder
  ) {}

  /* more code here */

  ngOnInit() {
    this.clients = this._route.snapshot.data['clients'].data;
    this.model = this._formBuilder.group({
      rate: [85, [Validators.required, Validators.pattern(this.FLOAT_REGEX)]],
      currency: ['CAD', [Validators.required, Validators.pattern(this.FLOAT_REGEX)]],
      client: [null, Validators.required],
      lineItems: this._formBuilder.array([
        this.initLineItem(),
      ]),
      notes: [null],
      discount: [null],
      discountUnit: ['$'],
    });
  }

  ngOnDestroy() {
    this.postInvoiceSubscription.unsubscribe();
  }

  initLineItem(): FormGroup {
    return this._formBuilder.group({
      description: [null],
      quantity: [null, Validators.pattern(this.FLOAT_REGEX)],
      total: [null, Validators.pattern(this.FLOAT_REGEX)]
    });
  }

  addAnItem() {
    const control = this.model.controls['lineItems'].value;
    control.push(this.initLineItem());
  }

  /* more code here */
}

Template (area that raises the error):

<div class="invoice-table__body">
  <ul class="list invoice-table__list-items">
    <li *ngFor="let item of model.controls.lineItems.controls; let i = index">
      <div formGroupName="i">
        <input
          type="text"
          formControlName="description"
          class="col-sm-8"
          placeholder="Item description...">

        <input
          type="number"
          class="col-sm-2"
          formControlName="quantity">

        <p
          *ngIf="item.quantity"
          class="col-sm-2 invoice-table__line-item-total">
          {{ calculateLineItemTotal(item, item.quantity) | currency:'USD':true }}
        </p>
      </div>
    </li>
  </ul>

  <button
    type="button"
    class="btn btn--primary invoice-table__add-row-btn"
    (click)="addAnItem()">
    Add an item
  </button>
</div>

1 Answer 1

1

Ok, found out why. I didn't notice the formArrayName attribute on the element that wrapped the *ngFor. That attribute's value should be the name of the property that holds the formBuilder.array. In my case formArrayName="lineItems".

Also, the formGroupName attribute should be wrapped in square brackets: [formGroupName]="i".

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.