简体   繁体   中英

How would I go about binding form input from multiple child components to an array defined in a parent component?

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 class="mb-3 row item">
<div class="col">
    <label for="item-name" class="form-label">Item Name</label>
    <input type="text" id="item-name" name="clientName" class="form-control">
</div>

<div class="col">
    <label for="quantity" class="form-label">Quantity</label>
    <input type="text" id="quantity" name="quantity" class="form-control">
</div>

<div class="col">
    <label for="price" class="form-label">Price</label>
    <input type="text" id="price" name="price" class="form-control">
</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
}

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)
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM