Customize Material Table by including a new column with inputs connected to a FormArray

I have a table containing material that is updated based on various filters. One filter specifically pulls data for unpaid invoices, and for these records, I've included a custom column with an input labeled payment_amount. This input field is not part of the main table's data source object because it is used to calculate two other fields within the data source. The process involves entering the payment amount when a check is received, which then triggers automatic calculations for the rate and fuel_surcharge, updating the record backend.

The challenge lies in organizing these input elements into a FormArray so that each has a unique reference for validation purposes and corresponds correctly to the record's id.

I'm unsure where to begin in terms of adding these inputs to a FormArray. While I understand that I need a FormGroup, I'm uncertain about placement, and although I know a FormArray declaration is necessary, populating it dynamically while rendering the table poses some questions. Any suggestions or ideas?

This is what I currently have:

<ng-container matColumnDef="payment_amount">
  <th mat-header-cell mat-sort-header *matHeaderCellDef> Payment Amount</th>
  <td mat-cell *matCellDef="let shipment; let index = index">
    <mat-form-field>
      <mat-label>Payment Amount</mat-label>
      <mat-icon inline matPrefix fontIcon="attach_money"></mat-icon>
      <input matInput required minLength="300" (blur)="calculateBill(shipment.id,$event.target.value)">
    </mat-form-field>
  </td>
</ng-container>

<ng-container matColumnDef="shipments.rate">
   <th mat-header-cell mat-sort-header *matHeaderCellDef> Rate</th>
    <td mat-cell *matCellDef="let shipment"> {{shipment.rate | currency}}</td>
</ng-container>

<ng-container matColumnDef="shipments.fuel_surcharge">
  <th mat-header-cell mat-sort-header *matHeaderCellDef> Fuel Surcharge</th>
  <td mat-cell *matCellDef="let shipment"> {{shipment.fuel_surcharge | currency}}</td>
</ng-container>

Within my component;

calculateBill(shipment_id, value){
  // Calculation logic for the two fields and updating table cells; view updates are functioning
  let index = this.dataSource.shipmentsResponse.data.findIndex({
    shipment => shipment.id === shipment_id
  });
  this.dataSource.shipments[index].fuel_surcharge = value - 300;
  this.dataSource.shipments[index].rate = 300;
}

Answer №1

To implement an editable Angular Material table, you can use the formArrayName="array" directive on the table and create a form control for each row. Check out the example below:

html

<form [formGroup]="formGroup">
  <table
    mat-table
    [dataSource]="dataSource"
    class="mat-elevation-z8"
    formArrayName="array"
  >
    <ng-container matColumnDef="payment_amount">
      <th mat-header-cell mat-sort-header *matHeaderCellDef>Payment Amount</th>
      <td mat-cell *matCellDef="let shipment; let index = index">
        <mat-form-field>
          <mat-label>Payment Amount</mat-label>
          <mat-icon inline matPrefix fontIcon="attach_money"></mat-icon>
          <input
            matInput
            [formControlName]="index"
            (blur)="calculateBill(shipment.id,$any($event!.target)!.value!)"
          />
        </mat-form-field>
      </td>
    </ng-container>

    <ng-container matColumnDef="rate">
      <th mat-header-cell mat-sort-header *matHeaderCellDef>Rate</th>
      <td mat-cell *matCellDef="let shipment">{{shipment.rate | currency}}</td>
    </ng-container>

    <ng-container matColumnDef="fuel_surcharge">
      <th mat-header-cell mat-sort-header *matHeaderCellDef>Fuel Surcharge</th>
      <td mat-cell *matCellDef="let shipment">
        {{shipment.fuel_surcharge | currency}}
      </td>
    </ng-container>
    <tr mat-header-row *matHeaderRowDef="displayColumns"></tr>
    <tr
      mat-row
      *matRowDef="let row; let i = index; columns: displayColumns;"
    ></tr>
  </table>
</form>

<!-- Copyright 2023 Google LLC. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at https://angular.io/license -->

{{formGroup.value | json}}

ts

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatCommonModule } from '@angular/material/core';
import { MatTableModule } from '@angular/material/table';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import {
  FormArray,
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';
import { MatInputModule } from '@angular/material/input';

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: any[] = [
  { id: 1, name: 'Hydrogen', payment_amount: 0, rate: 0, fuel_surcharge: 0 },
  { id: 2, name: 'Helium', payment_amount: 0, rate: 0, fuel_surcharge: 0 },
  { id: 3, name: 'Lithium', payment_amount: 0, rate: 0, fuel_surcharge: 0 },
  { id: 4, name: 'Beryllium', payment_amount: 0, rate: 0, fuel_surcharge: 0 },
];

/**
 * @title Basic use of `<table mat-table>`
 */
@Component({
  selector: 'table-basic-example',
  styleUrls: ['table-basic-example.css'],
  templateUrl: 'table-basic-example.html',
  standalone: true,
  imports: [
    MatTableModule,
    CommonModule,
    MatIconModule,
    MatFormFieldModule,
    ReactiveFormsModule,
    MatInputModule,
  ],
})
export class TableBasicExample {
  formGroup: FormGroup = new FormGroup({
    array: new FormArray([]),
  });
  displayColumns = ['payment_amount', 'rate', 'fuel_surcharge'];
  dataSource = ELEMENT_DATA;

  ngOnInit() {
    const arrayCtrl: FormArray = <FormArray>this.formGroup.get('array');

    this.dataSource.forEach((x: any) => {
      arrayCtrl!.push(
        new FormControl(x.payment_amount, [
          Validators.required,
          Validators.max(300),
        ])
      );
    });
  }

  calculateBill(shipment_id: any, value: any) {
    //calculates the two fields and updates the table cells. Updating the table view works
    let index = this.dataSource.findIndex(
      (shipment: any) => shipment.id === shipment_id
    );
    this.dataSource[index].fuel_surcharge = value - 300;
    this.dataSource[index].rate = 300;
  }
}

Check out the working demo on StackBlitz!


Citation

  1. Angular Material Table with Form Array

Answer №2

Though it may be a tad belated, I have crafted a node package specifically designed for a versatile table form component that could potentially prove beneficial to you. This solution opts not to utilize angular material tables, instead relying on standard html and bootstrap class names for the table structure. Please feel free to make use of the package in its current state or draw inspiration from it to create your own table form component.

The input consists of a mapping of column names and their respective validator functions, with the additional option of passing an array of objects as input. Should an array be provided, it will be transformed into a formArray and displayed on the table interface. Each formGroup within the formArray corresponds to a row in the table, while each formControl is associated with its corresponding column. An empty row is always present at the bottom of the table to facilitate the creation of new records.

Changes made to the table are automatically saved upon pressing the enter/esc key, when shifting focus away from the row, navigating to a new row, or clicking the save button.

The package triggers events for creating, editing, and deleting entries which can be handled based on your specific requirements within your angular application.

You can explore the package further by visiting https://www.npmjs.com/package/ng-table-form

To install the package, simply use the following command:

npm install ng-table-form

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilize existing *.resx translation files within a hybrid application combining AngularJS and Angular 5

Greetings! I currently have an AngularJS application that utilizes $translateProvider for internalization and WebResources.resx files: angular.module('app') .config(['$translateProvider', 'sysSettings', 'ngDialogProv ...

What is the process of molding items?

Here is an object that I have: { "id": "Test", "firstname": "Test", "lastname": "Test", "age": 83 } However, I only want to return the object with this value: { "id&quo ...

What is the process of 'initializing' an object in TypeScript?

Is it possible that retrieving a json from a mongodb database and casting it does not trigger the typescript constructor? What could be causing this issue? I have a Team class export class Team { transformations: { [transformationId: string]: Transfor ...

Tips for passing an array between components in Angular 2

My goal is to create a to-do list with multiple components. Initially, I have 2 components and plan to add more later. I will be sharing an array of tasks using the Tache class. Navbar Component import { Component } from '@angular/core'; impor ...

Webpack and incorporating bespoke scripts

Can someone please help me understand how webpack works? I'm currently working on an Angular 2 project with a webpack starter and I have some JavaScript scripts from AWS (my SDK for API Gateway). These are about 10 JS files that I currently have liste ...

Create a TypeScript Map with arrays as values

Is there a way to create a Map in TypeScript where each member contains an array of strings, all in a single statement? I attempted the following: private gridOptions: Map<string, Array<string>> = {"1": ["test"]}; However, I encountered this ...

How to handle an already initialised array in Angular?

I came across an interesting demo on exporting data to Excel using Angular 12. The demo can be found at the following link: This particular example utilizes an array within the component TypeScript file. import { Component } from '@angular/core' ...

Steps to invoke the Express JS code repository from a serverless repository

Within my current project, there exists various repositories housing Angular code, Express JS (node JS) code, and AWS serverless components. For a specific feature, I am tasked with transferring the 'userId' value from the Angular code to the ser ...

The combination of types in a union allows for incorrect assignments of properties from the individual types involved

I'm struggling to grasp the concept of unions in TypeScript. Can someone explain why the following assignment is considered valid? I believed it would only be acceptable for const a = {a:12}, {a:123,b:23}, or {a:12,b:12,c:123}. type abcd = | { ...

Unable to find solutions for all parameters in the Application Module: (?). Error occurred at syntaxError (compiler.js:1021) on the site stackoverflow.com

Upon reverting my Angular project from version 7 to 6 and integrating Angular Universal using @ngToolkit, a specific error message appeared in the browser console: The parameters for Application Module cannot be resolved: (?). at syntaxError (compiler.js: ...

How can I uniquely combine a code with an existing CSS class and make modifications to it?

I am using ngx-skeleton-loader and I would like to change the color, but I am facing some difficulties. Here is an image that illustrates the issue. When looking at the developer tools, you can see the styles action in the styles action bar. .loader ...

Strategies for preventing focus from shifting to tabbable elements within a chat component that has been minimized

There is a chat feature in my website that appears in a sidebar when the chat icon on the site header is clicked. To improve accessibility, I added tabindex = 0 to key elements such as the minimize and close icons on the chat container, as well as all th ...

The array is not empty but the length is being displayed as zero

I am facing an issue in my project where I can successfully print the array in console, but I am unable to retrieve the length and access the first element. Here is the code snippet: checkout.component.ts: ngOnInit() { this.booksInCheckout = this.ch ...

Newest version of Angular cli encounters issues when trying to initiate a new application in Windows

Attempting to create a new Angular app on my Windows 10 machine using the command ng new myapp (cli version 9.0.1) with Angular v9 and Node version v12.15.0. I have already tried several solutions from Stack Overflow, including: Uninstalling Angular CLI ...

Modify associated dropdown menus

I am trying to create an edit form that includes dependent select fields (such as country, state, city). The issue I am facing is that the edit only works when I reselect the first option (car brand) because I am using the event (change) with $event. How c ...

Encountering a Typescript issue with the updated Apollo server version within a NestJS application

After upgrading my nestJS application to use version 3.2 of apollo-server-plugin-base, I encountered two TypeScript errors related to a simple nestJS plugin: import { Plugin } from '@nestjs/graphql' import { ApolloServerPlugin, GraphQLRequest ...

What is the process of switching the dark theme on and off in an Angular application using

Looking to incorporate a bootstrap dark theme into my Angular app, but unsure of how to add the data-bs-theme="dark" attribute in the index.html file. Any suggestions? Struggling with dynamically changing the data-bs-theme attribute using Angular. Any tip ...

Having issues with D3js chart rendering: Unable to display bars or pie chart properly, only shows as

I've been delving into D3js and I'm encountering an issue with the script below. It's only generating an SVG with a vertical line, and I'm struggling to figure out why. Within my data item called Fight, there is a property that gathers ...

Transfer websites to subfolders without altering the base URL

Previously, I had two staging/test servers each hosting an angular application on nginx, referred to as app1 and app2. The setup was straightforward: http://<ip for app1 server>/ => app1 http://<ip for app2 server>/ => app2 Now, my g ...

Displaying data from a JSON object to a DOM table can be achieved by dynamically generating table columns based on the keys within

There is a JSON object that I'm working with: [ { "SysID": "4", "Defect Classification": "Wrong Image Color", "1": "3.0", "2": "", "3": " ...