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.