Home > Mobile >  How would I go about binding form input from multiple child components to an array defined in a pare
How would I go about binding form input from multiple child components to an array defined in a pare

Time:06-17

I am trying to bind form input from multiple child components (item-input-component) to array itemList[] defined in a parent component (add-invoice-component). I want to take three inputs (itemName, quantity, price), create an Item object out of them, then output that Item object from item-input-component and add it to itemList[]. How can I do that ? Is that even possible ? And if not, what's a good solution ?

add-invoice-component.html

 <form> 
    <div>
        <h2>Items</h2>
        <app-item-input *ngFor="let item of numArray"></app-item-input>
        <button (click)="incrementNumOfItems()">Add Item</button>
    </div>
</form>

add-invoice-component.ts

import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';
import { Item } from 'src/app/models/item';

@Component({
  selector: 'app-add-invoice',
  templateUrl: './add-invoice.component.html',
  styleUrls: ['./add-invoice.component.css']
})
export class AddInvoiceComponent implements OnInit {

  description!: string;
  invoiceDate!: Date;
  clientName!: string;
  clientAddress!: string;
  itemList!: Item[];
  numOfItems: number = 2;
  numArray: number[] = Array(2).fill(0).map((x,i)=>i);

  constructor( private el: ElementRef) { }


  ngOnInit(): void {
  }

  incrementNumOfItems() {
    this.numOfItems  ;
    this.numArray = Array(this.numOfItems).fill(0).map((x,i)=>i);
  }


  removeItem() {
    this.el.nativeElement.remove()
  }
}

item-input-component.html

<div >
<div >
    <label for="item-name" >Item Name</label>
    <input type="text" id="item-name" name="clientName" >
</div>

<div >
    <label for="quantity" >Quantity</label>
    <input type="text" id="quantity" name="quantity" >
</div>

<div >
    <label for="price" >Price</label>
    <input type="text" id="price" name="price" >
</div>

<button (click)="removeItem()">Delete Item</button>

item-input-component.ts

import { Component, ElementRef, OnInit , Renderer2} from '@angular/core';

@Component({
  selector: 'app-item-input',
  templateUrl: './item-input.component.html',
  styleUrls: ['./item-input.component.css']
})
export class ItemInputComponent implements OnInit {

  itemName!: string;
  quantity!: number;
  price!: number;

  constructor(private el: ElementRef) { }

  ngOnInit(): void {
  }

  removeItem() {
    this.el.nativeElement.remove()
  }
}

Item.ts

export interface Item {
    itemName: string,
    quantity: number,
    price: number
}

CodePudding user response:

The question is about pass to your child component an object, to Delete you need use Output to comunicate with the parent

This object will be an object with properties: itemName, quantity and price -and the you use [(ngModel)] or a FormGroup -and you use ReactiveForms-

  @Input()element:any
  @Output() deleteItem:EventEmitter<any>:new EventEmitter<any>()

<input ... [(ngModel)]="element.clientName">
<input ... [(ngModel)]="element.quantity">
<input ... [(ngModel)]="element.price">

<button (click)="deleteItem.emit(null)">Delete Item</button>

Your parent

<app-item-input *ngFor="let item of list;let i=index"
   [element]="list[i]"
   (deleteItem)="removeItem(i)"
></app-item-input>

where

list:any[]=this.numArray.map(_=>({
     clientName:'',
     quantity:0,
     price:0
}))

//see that removeItem remove one value of the array.
//**NOT** about your .html
removeItem(index:number) {
  this.list.splice(index,1)
}

Using a FormArray is looks like

  formGroup:FormGroup;
  @Input('group') set _group(value)
  {
     this.formGroup=value as FormGroup
  }

  @Output() deleteItem:EventEmitter<any>:new EventEmitter<any>()

<form [formGroup]="fromGroup">
 <input ... formControlName="clientName">
 <input ... formControlName="quantity">
 <input ... formControlName="price">
</form>

<button (click)="deleteItem.emit(null)">Delete Item</button>

And the parent

<app-item-input *ngFor="let item of listFormArray.controls;let i=index"
   [group]="listFormArray.at(i)"
   (deleteItem)="removeItem(i)"
></app-item-input>

listFormArray:FormArray[]=new FormArray(
  this.numArray.map(_=>(new FormGroup({
     clientName:new FormControl(null),
     quantity:new FormControl(0),
     price::new FormControl(0)
  })))
)

removeItem(index:number) {
  this.formArray.removeAt(index)
}

CodePudding user response:

Looks like you're not familiar with Angular services, so here's an example to show you how it works.

Stackblitz: https://stackblitz.com/edit/angular-ivy-afppeb?file=src/app/item.service.ts


Here's a simple service to hold the items, it has add and delete methods

Item Service

@Injectable({ providedIn: 'root' })
export class ItemService {
  itemList: Item[] = [];

  addItem(item: Item) {
    this.itemList.push(item);
  }

  deleteItem(index: number) {
    this.itemList.splice(index, 1);
  }
}

To access this service you just inject it via the constructor of any component:

Add Invoice Component

export class AddInvoiceComponent {
  constructor(private itemService: ItemService) {}

  get items() {
    return this.itemService.itemList;
  }

  delete(index: number) {
    this.itemService.deleteItem(index);
  }
}
<app-item-input></app-item-input>
<h1>Items</h1>
<ng-container *ngFor="let item of items; index as i">
  <pre>{{ item | json }}</pre>
  <button (click)="delete(i)">Delete</button>
</ng-container>

And here's a simple way to convert the form data to an object

Item Input Component

export class ItemInputComponent {
  formGroup = new FormGroup({
    itemName: new FormControl(''),
    quantity: new FormControl(0),
    price: new FormControl(0),
  });

  constructor(private itemService: ItemService) {}

  onSubmit() {
    this.itemService.addItem(this.formGroup.getRawValue());
  }
}
<h1>Input</h1>
<form [formGroup]="formGroup" (ngSubmit)="onSubmit()">
  <div>
    <label>Item Name:</label>
    <input type="text" formControlName="itemName" />
  </div>
  <div>
    <label>Quantity:</label>
    <input type="number" formControlName="quantity" />
  </div>
  <div>
    <label>Price:</label>
    <input type="number" formControlName="price" />
  </div>
  <button type="submit">Submit</button>
</form>

You may want to do form validation as well, but this is just to show you how the service works.

  • Related