Batch Editing in Angular Data Grid

5 Sep 202624 minutes to read

Batch editing is a powerful feature in the Data Grid component that enables simultaneous modification and saving of multiple cells in a single action. This feature provides an efficient way to make bulk changes without saving each change individually, making it particularly useful for large datasets.

Enable batch editing

To enable batch editing mode, set the editSettings.mode property to Batch. When activated, double-clicking a cell enters edit mode, and bulk updates can be saved via the toolbar’s Update button or by invoking the batchSave method.

The following example demonstrates enabling batch editing in the Data Grid component:

import { data } from './datasource';
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { EditService, EditSettingsModel, GridModule, PageService, SortService, ToolbarItems, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';

@Component({
    imports: [
        GridModule,
        DatePickerAllModule,
        FormsModule,
        TimePickerModule,
        FormsModule,
        TextBoxModule,
        MultiSelectModule,
        AutoCompleteModule
        ],
    providers: [EditService, ToolbarService, SortService, PageService],
    standalone: true,
    selector: 'app-root',
    template: `<ejs-grid [dataSource]='data' [editSettings]='editSettings' 
               [toolbar]='toolbar' height='273px'>
                <e-columns>
                    <e-column field='OrderID' headerText='Order ID' [validationRules]='orderIDRules' 
                    textAlign='Right' isPrimaryKey='true' width=100></e-column>
                    <e-column field='CustomerID' headerText='Customer ID' 
                    [validationRules]='customerIDRules' width=120></e-column>
                    <e-column field='Freight' headerText='Freight' textAlign= 'Right'
                    editType= 'numericedit' [validationRules]='freightRules' 
                    width=120 format= 'C2'></e-column>
                    <e-column field='ShipCountry' headerText='Ship Country' 
                    editType= 'dropdownedit' [validationRules]='shipCountryRules'  width=150></e-column>
                </e-columns>
                </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    public editSettings?: EditSettingsModel;
    public toolbar?: ToolbarItems[];
    public orderIDRules?: Object;
    public customerIDRules?: Object;
    public freightRules?: Object;
    public shipCountryRules?: Object;

    ngOnInit(): void {
        this.data = data;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' };
        this.toolbar = ['Add','Delete', 'Update', 'Cancel'];
        this.orderIDRules = { required: true, number: true };
        this.customerIDRules = { required: true };
        this.freightRules =  { min:1, max:1000 };
        this.shipCountryRules = { required: true };
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Prevent editing based on conditions

Batch edit mode provides control over CRUD operations by allowing specific rows or cells to be protected from editing, adding, or deletion. This data integrity feature ensures only authorized modifications proceed.

The following table summarizes the events and cancellation methods available for each operation:

Operation Event Trigger Point Cancellation Method
Edit cellEdit When a cell enters edit mode Set args.cancel to true
Add beforeBatchAdd Before a new record is added Set args.cancel to true
Delete beforeBatchDelete Before a record is deleted Set args.cancel to true

Each event handler receives operation context, allowing condition-based logic to block or permit the action.

In the following demo, CRUD operations are prevented based on the “Role” column value. When the “Role” column contains “Admin”, edit and delete actions are prevented for that row.

import { columnDataType, data } from './datasource';
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { BeforeBatchAddArgs, BeforeBatchDeleteArgs, CellEditArgs, EditService, EditSettingsModel, GridModule, PageService, SortService, ToolbarItems, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';

@Component({
    imports: [ 
        GridModule,
        DatePickerAllModule,
        FormsModule,
        TimePickerModule,
        FormsModule,
        TextBoxModule,
        MultiSelectModule,
        AutoCompleteModule,
        ButtonModule
        ],
    providers: [EditService, ToolbarService, SortService, PageService],
    standalone: true,
    selector: 'app-root',
    template: `<div style="padding:0px 0px 20px 0px">
                <button ejs-button id="sample" (click)="btnClick($event)">Grid is Addable</button> 
               </div> 
               <ejs-grid  [dataSource]='data' [editSettings]='editSettings' [toolbar]='toolbar' (cellEdit)="cellEdit($event)" 
               (beforeBatchAdd)="beforeBatchAdd($event)" (beforeBatchDelete)="beforeBatchDelete($event)" height='240px'>
                <e-columns>
                    <e-column field='OrderID' headerText='Order ID' textAlign='Right' [validationRules]='orderIDRules' 
                    isPrimaryKey='true' width=100></e-column>
                    <e-column field='Role' headerText='Role' [validationRules]='roleRules' width=120></e-column>
                    <e-column field='Freight' headerText='Freight' textAlign= 'Right'
                     editType= 'numericedit' width=120 [validationRules]='freightRules' format= 'C2'></e-column>
                    <e-column field='ShipCountry' headerText='Ship Country' editType= 'dropdownedit' width=150></e-column>
                </e-columns>
               </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    public editSettings?: EditSettingsModel;
    public toolbar?: ToolbarItems[];
    public isAddable?: boolean = true;
    public orderIDRules?: Object;
    public roleRules?: Object;
    public freightRules?: Object;

    ngOnInit(): void {
        this.data = data;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' };
        this.toolbar = ['Add', 'Delete', 'Update', 'Cancel'];
        this.orderIDRules = { required: true, number: true };
        this.roleRules = {required: true };
        this.freightRules =  { min:1, max:1000 };
    }
    cellEdit(args: CellEditArgs) {
        if ((args.rowData as columnDataType)['Role'] == 'Admin') {
            args.cancel = true;
        }
    }
    beforeBatchAdd(args: BeforeBatchAddArgs) {
        if (!this.isAddable) {
            args.cancel = true;
        }
    }
    beforeBatchDelete(args: BeforeBatchDeleteArgs) {
        if ((args.rowData as columnDataType)['Role'] == 'Admin') {
            args.cancel = true;
        }
    }
    btnClick(args: MouseEvent) {
        (args.target as HTMLElement).innerText == 'GRID IS ADDABLE' ? ((args.target as HTMLElement).innerText = 'Grid is Not Addable') : ((args.target as HTMLElement).innerText = 'Grid is Addable');
        this.isAddable = !this.isAddable;
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Automatic update column values

Synchronize column values dynamically by calculating and updating dependent columns in real-time as related columns are edited. This is particularly useful for computed fields like totals, discounts, or derived metrics that depend on other cell values. This can be achieved using the Cell Edit Template feature, helping maintain data consistency without manual recalculation.

In the following example, the “Total Cost” column value is updated based on changes to the “Unit Price” and “Units In Stock” columns.

import { productData } from './datasource';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { EditService, GridModule, PageService, SortService, ToolbarService , GridComponent, EditSettingsModel, ToolbarItems, IEditCell, CellEditArgs} from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';
import { ChangeEventArgs, NumericTextBox } from '@syncfusion/ej2-inputs';

@Component({
  imports: [
      GridModule,
      DatePickerAllModule,
      FormsModule,
      TimePickerModule,
      FormsModule,
      TextBoxModule,
      MultiSelectModule,
      AutoCompleteModule
      ],
  providers: [EditService, ToolbarService, SortService, PageService],
  standalone: true,
  selector: 'app-root',
  template: `
      <ejs-grid #grid id="grid" [dataSource]="data" [editSettings]="editSettings" [toolbar]="toolbar" 
      height="273px" (cellEdit)="cellEdit($event)">
        <e-columns>
          <e-column field="ProductID" headerText="Product ID" textAlign="Right" isPrimaryKey="true" 
          width="100"></e-column>
          <e-column field="ProductName" headerText="Product Name" width="120"></e-column>
          <e-column field="UnitPrice" headerText="Unit Price" [edit]="priceParams" width="150" 
          format="C2" textAlign="Right"></e-column>
          <e-column field="UnitsInStock" headerText="Units In Stock" [edit]="stockParams" 
          width="150" textAlign="Right"></e-column>
          <e-column field="TotalCost" headerText="Total Cost" width="150" format="C2" 
          textAlign="Right"></e-column>
        </e-columns>
      </ejs-grid> `
})
export class AppComponent implements OnInit {

  @ViewChild('grid')
  public grid?: GridComponent;
  public data?: Object[];
  public editSettings?: EditSettingsModel;
  public toolbar?: ToolbarItems[];
  public priceParams?: IEditCell;
  public stockParams?: IEditCell;
  public priceElem?: HTMLElement;
  public priceObj?: NumericTextBox;
  public stockElem?: HTMLElement;
  public stockObj?: NumericTextBox;

  ngOnInit(): void {
    this.data = productData;
    this.editSettings = {
      allowEditing: true,
      allowAdding: true,
      allowDeleting: true,
      mode: 'Batch'
    };
    this.toolbar = ['Add', 'Delete', 'Update', 'Cancel'];
    this.priceParams = {
      create: () => {
        this.priceElem = document.createElement('input');
        return this.priceElem;
      },
      read: () => {
        return this.priceObj?.value;
      },
      destroy: () => {
        this.priceObj?.destroy();
      },
      write: (args: any) => {
        var rowData = args.rowData;
        var rowIndex = (this.grid as GridComponent).getRowInfo(args.row).rowIndex;
        this.priceObj = new NumericTextBox({
          value: args.rowData[args.column.field],
          change: ((args: ChangeEventArgs) => {
            var totalCostValue = (args.value as number) * rowData['UnitsInStock'];
            (this.grid as GridComponent).updateCell((rowIndex as number), 'TotalCost', totalCostValue);
          }).bind(this)
        });
        this.priceObj?.appendTo(this.priceElem);
      }
    };
    this.stockParams = {
      create: () => {
        this.stockElem = document.createElement('input');
        return this.stockElem;
      },
      read: () => {
        return (this.stockObj as  NumericTextBox).value;
      },
      destroy: () => {
        (this.stockObj as  NumericTextBox).destroy();
      },
      write: (args: any) => {
        var rowData = args.rowData;
        var rowIndex = (this.grid as GridComponent).getRowInfo(args.row).rowIndex;
        this.stockObj = new NumericTextBox({
          value: args.rowData[args.column.field],
          change: ((args: ChangeEventArgs) => {
            var totalCostValue = (args.value as number) * rowData['UnitPrice'];
            (this.grid as GridComponent).updateCell((rowIndex as number), 'TotalCost', totalCostValue);
          }).bind(this)
        });
        this.stockObj.appendTo(this.stockElem);
      }
    };
  }
  cellEdit(args: CellEditArgs) {
    if (args.columnName == 'TotalCost') {
      args.cancel = true;
    }
  }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

The updateCell method enables programmatic updates to cells in batch mode.

Add new rows at the bottom of the grid

The Data Grid can be configured to add new rows at the bottom of the data set, making it convenient to insert new records at the end. Set the newRowPosition property in editSettings to Bottom to activate this option.

  • When newRowPosition is set to Bottom, the TAB key facilitates data entry across cells or rows while in edit mode. Entering data and pressing TAB automatically creates new rows below the current one, allowing efficient entry for multiple records.
  • When newRowPosition is set to Top, a blank row is displayed at the top for data entry. Upon saving, the new record appears at the end of the grid.
  • If paging is enabled, updating the row causes it to move to the last page, depending on page size. This behavior applies to both local and remote data sources.
  • With scrolling enabled, the TAB key allows adding new rows, even beyond the visible portion of the grid.
  • The newRowPosition property is supported in both Normal and Batch editing modes.

The following example illustrates enabling the addition of new rows at the bottom of the grid with the newRowPosition property:

import { data } from './datasource';
import { Component, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, DropDownListModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { EditService, GridComponent, GridModule, NewRowPosition, PageService, SortService, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';
import { ChangeEventArgs } from '@syncfusion/ej2-dropdowns';

@Component({
    imports: [
            GridModule,
            DatePickerAllModule,
            FormsModule,
            TimePickerModule,
            FormsModule,
            TextBoxModule,
            MultiSelectModule,
            AutoCompleteModule,
            DropDownListModule
        ],
    providers: [EditService, ToolbarService, SortService, PageService],
    standalone: true,
    selector: 'app-root',
    template: `
        <div style="display: flex">
            <label style="padding: 5px 5px 0 0;"> Select new row position:</label>
            <ejs-dropdownlist index="0" width="100" 
            [dataSource]="positionData" (change)="changePosition($event)">
            </ejs-dropdownlist>
      </div>
      <div style="padding-top:5px">
        <ejs-grid #batchgrid id='Batchgrid' [dataSource]='data' allowPaging='true' [pageSettings]='pageSettings' [editSettings]='editSettings' [toolbar]='toolbar' >
            <e-columns>
                <e-column field='OrderID' headerText='Order ID' width='120' textAlign='Right' isPrimaryKey='true' [validationRules]='orderIDRules'></e-column>
                <e-column field='CustomerID' headerText='Customer ID' width='120' [validationRules]='customerIDRules'></e-column>
                <e-column field='Freight' headerText='Freight' width='120' format='C2' textAlign='Right' editType='numericedit' [validationRules]='freightRules'></e-column>
                <e-column field='OrderDate' headerText='Order Date' width='130' format='yMd' editType='datepickeredit' textAlign='Right'></e-column>
                <e-column field='ShipCountry' headerText='Ship Country' width='150' editType='dropdownedit' [edit]='editparams'></e-column>
            </e-columns>
        </ejs-grid>
      </div>
`,
})
export class AppComponent {

    public data?: Object[];
    @ViewChild('batchgrid')
    public grid?: GridComponent;
    public editSettings?: Object;
    public toolbar?: string[];
    public orderIDRules?: Object;
    public customerIDRules?: Object;
    public freightRules?: Object;
    public editparams?: Object;
    public pageSettings?: Object;
    public positionData: Object[] = [
        { text: 'Top', value: 'Top' },
        { text: 'Bottom', value: 'Bottom' },

    ];
    public ngOnInit(): void {
        this.data = data;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' };
        this.toolbar = ['Add', 'Delete', 'Update', 'Cancel'];
        this.orderIDRules = { required: true, number: true };
        this.customerIDRules = { required: true };
        this.freightRules = { required: true };
        this.editparams = { params: { popupHeight: '300px' } };
        this.pageSettings = { pageCount: 5 };
    }
    public changePosition(args: ChangeEventArgs): void {
        (this.grid as GridComponent).editSettings.newRowPosition = args.value as NewRowPosition;
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Single-click editing

The Data Grid can be configured for single-click cell editing, streamlining data entry without requiring double-clicks. To enable single-click editing, handle the created event and bind a click event to grid cells that calls the editCell method on click.

The following example shows single-click editing by using the created event and the editCell method:

import { data } from './datasource';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { EditService, EditSettingsModel, GridComponent, GridModule, PageService, SortService, ToolbarItems, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';
import { isNullOrUndefined } from '@syncfusion/ej2-base';

@Component({
    imports: [
        GridModule,
        DatePickerAllModule,
        FormsModule,
        TimePickerModule,
        FormsModule,
        TextBoxModule,
        MultiSelectModule,
        AutoCompleteModule
    ],
    providers: [EditService, ToolbarService, SortService, PageService],
    standalone: true,
    selector: 'app-root',
    template: `<ejs-grid  #grid id="grid" [dataSource]='data' height='220px' [allowPaging]="true" [enableHover]="false" [editSettings]='editSettings' [toolbar]='toolbar'
    (created)="created()" (load)="load()">
                <e-columns>
                    <e-column field='OrderID' headerText='Order ID' textAlign='Right'
                     isPrimaryKey='true' [validationRules]='orderIDrules' visible='false' width=120></e-column>
                    <e-column field='CustomerID' [validationRules]='customerIDrules' headerText='Customer ID' width=120></e-column>
                    <e-column field='Freight' headerText='Freight' [validationRules]='freightrules' format='C2' textAlign='Right' width=150></e-column>
                    <e-column field='OrderDate' headerText='Order Date' editType='datepickeredit' [validationRules]='orderDaterules' format='yMd' width=150></e-column>
                    <e-column field='ShipCountry' [validationRules]='shipCountryrules' headerText='Ship Country' width=150></e-column>
                </e-columns>
                </ejs-grid>`
})
export class AppComponent implements OnInit {

    public data?: object[];
    public editSettings?: EditSettingsModel;
    public toolbar?: ToolbarItems[];
    @ViewChild('grid')
    public grid?: GridComponent;
    public orderIDrules?: Object;
    public customerIDrules?: Object;
    public freightrules?: Object;
    public shipCountryrules?: Object;
    public orderDaterules?: Object;

    ngOnInit(): void {
        this.data = data;
        this.editSettings = { allowEditing: true, allowAdding: true, allowDeleting: true, mode: 'Batch' },
        this.toolbar = ['Add', 'Delete', 'Update', 'Cancel'];
        this.orderIDrules = { required: true, number: true };
        this.customerIDrules = { required: true };
        this.freightrules = { min: 1, max: 1000 };
        this.shipCountryrules = { required: true };
        this.orderDaterules = { required: true };
    }
    created = () => {
        (this.grid as GridComponent).getContentTable().addEventListener('click', (args) => {
            if ((args.target as HTMLElement).classList.contains('e-rowcell')) {
                (this.grid as GridComponent).editModule.editCell(parseInt(((args.target as HTMLElement).getAttribute('index') as string)),
                    (this.grid as GridComponent).getColumnByIndex(parseInt((args.target as HTMLElement).getAttribute('aria-colindex') as string) - 1).field);
            }
        });
    };
    load = () => {
        (this.grid as GridComponent).element.addEventListener('keydown', (e) => {
            var closesttd = (e.target as HTMLElement).closest('td');
            if (e.keyCode === 39 && !isNullOrUndefined(((closesttd as HTMLTableCellElement).nextSibling as HTMLElement))) {
                this.editACell(((closesttd as HTMLTableCellElement).nextSibling as HTMLElement));
            }
            if (e.keyCode === 37 && !isNullOrUndefined(((closesttd as HTMLTableCellElement).previousSibling as HTMLElement)) &&
                !(this.grid as GridComponent).getColumnByIndex(
                    parseInt((((closesttd as HTMLTableCellElement).previousSibling as HTMLElement).getAttribute('aria-colindex') as string)) - 1).isPrimaryKey) {
                this.editACell(((closesttd as HTMLTableCellElement).previousSibling as HTMLElement));
            }
            if (e.keyCode === 40 && !isNullOrUndefined((((closesttd as HTMLTableCellElement).closest('tr') as HTMLTableRowElement).nextSibling as HTMLElement))) {
                this.editACell(
                    (((closesttd as HTMLTableCellElement).closest('tr') as HTMLTableRowElement).nextSibling as HTMLElement).querySelectorAll('td')[
                    parseInt(((closesttd as HTMLTableCellElement).getAttribute('aria-colindex') as string)) - 1]);
            }
            if (e.keyCode === 38 && !isNullOrUndefined((((closesttd as HTMLTableCellElement).closest('tr') as HTMLTableRowElement).previousSibling as ChildNode))) {
                this.editACell(
                    (((closesttd as HTMLTableCellElement).closest('tr') as HTMLTableRowElement).previousSibling as HTMLElement).querySelectorAll('td')[
                    parseInt(((closesttd as HTMLTableCellElement).getAttribute('aria-colindex') as string)) - 1]);
            }
        });
    };
    public editACell(args: HTMLElement) {
        (this.grid as GridComponent).editModule.editCell(
            parseInt((args.getAttribute('index') as string)),
            (this.grid as GridComponent).getColumnByIndex(parseInt(args.getAttribute('aria-colindex') as string) - 1).field);
    }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Enable undo / redo

The Data Grid supports Undo / Redo actions in batch editing, allowing changes such as cell edits, row additions, and row deletions to be reverted or reapplied before saving. This feature can be enabled by setting the editSettings.enableUndoRedo property to true.

Undo and redo operations can be performed using keyboard shortcuts. Press Ctrl + Z to undo an action, and Ctrl + Y to redo an action.

In addition, Undo and Redo items can be added to the Data Grid toolbar to perform these actions through interaction.

Here’s an example that demonstrates how to enable Undo / Redo in batch editing:

import { inventoryStoreData } from './datasource';
import { Component, OnInit, ViewChild } from '@angular/core';
import { EditService, GridComponent, GridModule, PageService, ToolbarService } from '@syncfusion/ej2-angular-grids';

@Component({
  imports: [GridModule],
  providers: [EditService, ToolbarService, PageService],
  standalone: true,
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {

  @ViewChild('normalgrid')
  public grid?: GridComponent;
  public inventoryStoreData?: Object[];
  public editSettings?: Object;
  public toolbar?: string[];
  public idRules?: Object;
  public unitPriceRules?: Object;
  public numericEditParams?: Object;
  public editparams?: Object;

  public ngOnInit(): void {
    this.inventoryStoreData = inventoryStoreData;
    this.editSettings = {
      allowEditing: true,
      allowAdding: true,
      allowDeleting: true,
      mode: 'Batch',
      enableUndoRedo: true
    };
    this.toolbar = ['Add', 'Delete', 'Update', 'Cancel', 'Undo', 'Redo'];
    this.idRules = { required: true, number: true };
    this.unitPriceRules = { required: true, min: 1 };
    this.numericEditParams = { params: { showSpinButton: false } };
    this.editparams = { params: { popupHeight: '300px' } };
  }
}
<div class="control-section" >
    <div class="col-lg-9">
      <ejs-grid #normalgrid id="Normalgrid" height='220px' [dataSource]="inventoryStoreData" allowPaging="true" 
      [editSettings]="editSettings" [toolbar]="toolbar">
        <e-columns>
          <e-column field="ID" headerText="ID" width="120" textAlign="Right" isPrimaryKey="true"
          [validationRules]="idRules"></e-column>
          <e-column field="Product" headerText="Product Name" width="180" editType="dropdownedit" [edit]="editparams"></e-column>
          <e-column field="VendorA" headerText="Vendor A (units)" width="160" textAlign="Right"
          editType="numericedit" [edit]="numericEditParams"></e-column>
          <e-column field="VendorB" headerText="Vendor B (units)" width="160" textAlign="Right"
          editType="numericedit" [edit]="numericEditParams"></e-column>
          <e-column field="VendorC" headerText="Vendor C (units)" width="160" textAlign="Right"
          editType="numericedit" [edit]="numericEditParams"></e-column>
          <e-column field="VendorD" headerText="Vendor D (units)" width="160" textAlign="Right"
          editType="numericedit" [edit]="numericEditParams"></e-column>
          <e-column field="UnitPrice" headerText="Price (per unit)" width="160" textAlign="Right"
          format="C2" editType="numericedit" [edit]="numericEditParams" [validationRules]="unitPriceRules"></e-column>
        </e-columns>
      </ejs-grid>
    </div>
  </div>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Note: The maximum number of undo and redo actions stored in the stack can be controlled using the editSettings.undoRedoLimit property. This property limits the number of actions that can be reverted or reapplied during a batch editing session. The default value is 20.

Save changes immediately

The Data Grid enables immediate saving or updating of batch mode changes, removing the necessity for a separate Save button. This supports efficient data editing workflows and instant feedback upon cell modification. The cellSaved event, in conjunction with the batchSave method, facilitates this approach.

By default, invoking the batchSave method displays a confirmation dialog. This dialog is used to verify the correctness of the changes being saved or canceled.

The cellSaved event triggers after saving a cell, allowing execution of custom logic upon cell save or update.

The batchSave method, provided by the grid’s edit service, commits all added, edited, and deleted records in batch mode.

To bypass the confirmation dialog when using batchSave, set editSettings.showConfirmDialog to false. Note that editSettings.mode must be configured as Batch for this property to take effect. This combination enables immediate persistence of changes without confirmation.

The following example demonstrates immediate saving or updating using the cellSaved event and the batchSave method:

import { data } from './datasource';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { CellSaveArgs, EditService, GridComponent, GridModule, PageService, SortService, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';

@Component({
  imports: [ 
          GridModule,
          DatePickerAllModule,
          FormsModule,
          TimePickerModule,
          FormsModule,
          TextBoxModule,
          MultiSelectModule,
          AutoCompleteModule
      ],
  providers: [EditService, ToolbarService, SortService, PageService],
  standalone: true,
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {

  @ViewChild('normalgrid')
  public grid?: GridComponent;
  public data?: Object[];
  public editSettings?: Object;
  public toolbar?: string[];
  public orderIDRules?: Object;
  public customerIDRules?: Object;
  public freightRules?: Object;
  public editparams?: Object;
  public pageSettings?: Object;
  public formatoptions?: Object;

  public ngOnInit(): void {
    this.data = data.slice(0, 15);
    this.editSettings = {
      allowEditing: true,
      allowAdding: true,
      allowDeleting: true,
      showConfirmDialog:false,
      mode: 'Batch',
    };
    this.toolbar = ['Add', 'Delete'];
    this.orderIDRules = { required: true, number: true };
    this.customerIDRules = { required: true };
    this.freightRules = { required: true };
    this.editparams = { params: { popupHeight: '300px' } };
    this.pageSettings = { pageCount: 5 };
    this.formatoptions = { type: 'dateTime', format: 'M/d/y hh:mm a' };
  }
  save(args: CellSaveArgs) {
    this.grid?.editModule.batchSave();
  }
}
<div class="control-section" >
    <div class="col-lg-9">
      <ejs-grid #normalgrid id="Normalgrid" height='220px' [dataSource]="data" allowPaging="true" [pageSettings]="pageSettings"
      [editSettings]="editSettings" [toolbar]="toolbar" (cellSaved)="save($event)">
        <e-columns>
          <e-column field="OrderID" headerText="Order ID" width="140" textAlign="Right" isPrimaryKey="true"
          [validationRules]="orderIDRules"></e-column>
          <e-column field="CustomerID" headerText="Customer ID" width="140"
          [validationRules]="customerIDRules"></e-column>
          <e-column field="Freight" headerText="Freight" width="140" format="C2" textAlign="Right"
          editType="numericedit" [validationRules]="freightRules"></e-column>
          <e-column field="OrderDate" headerText="Order Date" width="120" editType="datetimepickeredit"
          [format]="formatoptions" textAlign="Right"></e-column>
          <e-column field='ShipCountry' headerText='Ship Country' width='150' editType='dropdownedit' 
          [edit]='editparams'></e-column>
        </e-columns>
      </ejs-grid>
    </div>
  </div>
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Confirmation dialog

Confirmation dialogs provide an additional layer of protection when performing critical actions in batch editing mode. The Data Grid component offers built-in confirmation dialogs for the following scenarios:

Action Confirmation Trigger
Save Changes Appears when clicking Update button in toolbar.
Cancel Changes Appears when clicking Cancel button to discard edits.
Delete Record Appears when deleting a row during batch mode.

Enable the confirmation dialog by setting editSettings.showConfirmDialog to true in the editSettings configuration. The default value is true.

  • editSettings.showConfirmDialog requires editSettings.mode to be set to Batch.
  • Setting editSettings.showConfirmDialog to false disables the confirmation dialog in batch editing mode.
  • During updates and deletions, a dedicated delete confirmation dialog appears when selecting the delete button or pressing the delete key.

The following example demonstrates enabling or disabling the confirmation dialog using the showConfirmDialog property:

import { data } from './datasource';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SwitchModule } from '@syncfusion/ej2-angular-buttons';
import { DatePickerAllModule, TimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { AutoCompleteModule, MultiSelectModule } from '@syncfusion/ej2-angular-dropdowns';
import { EditService, EditSettingsModel, GridComponent, GridModule, PageService, SortService, ToolbarItems, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';
import { ChangeEventArgs } from '@syncfusion/ej2-buttons';

@Component({
  imports: [
      GridModule,
      DatePickerAllModule,
      FormsModule,
      TimePickerModule,
      FormsModule,
      TextBoxModule,
      MultiSelectModule,
      AutoCompleteModule,
      SwitchModule
      ],
  providers: [EditService, ToolbarService, SortService, PageService],
  standalone: true,
  selector: 'app-root',
  template: `
      <div style="padding: 5px">
        <label>Enable/Disable show confirmation dialog</label>
        <ejs-switch id="switch" [(checked)]="enableShowConfirmDialog" 
        (change)="toggleShowConfirmDialog($event)"></ejs-switch>
      </div>
      <div style="padding: 5px">
        <label>Enable/Disable show delete confirmation dialog</label>
        <ejs-switch id="switch1" [(checked)]="enableShowDeleteConfirmDialog" 
        (change)="toggleShowDeleteConfirmDialog($event)"></ejs-switch>
      </div>
      <ejs-grid #grid [dataSource]="data" [editSettings]="editSettings" [toolbar]="toolbar" height="220px">
        <e-columns>
          <e-column field="OrderID" headerText="Order ID" textAlign="Right" [validationRules]="orderIDRules"
            isPrimaryKey="true" width="100"></e-column>
          <e-column field="CustomerID" headerText="Customer ID" [validationRules]="customerIDRules" width="120"></e-column>
          <e-column field="Freight" headerText="Freight" textAlign="Right" editType="numericedit" width="120" format="C2"
            [validationRules]="freightRules"></e-column>
          <e-column field="ShipCountry" headerText="Ship Country" editType="dropdownedit" width="150"></e-column>
        </e-columns>
      </ejs-grid> `
})
export class AppComponent implements OnInit {

  @ViewChild('grid')
  public grid?: GridComponent;
  public data: object[] = [];
  public editSettings: EditSettingsModel = {};
  public toolbar: ToolbarItems[] = ['Add', 'Delete', 'Update', 'Cancel'];
  public orderIDRules: Object = {};
  public customerIDRules: Object = {};
  public freightRules: Object = {};
  public enableShowConfirmDialog: boolean = true;
  public enableShowDeleteConfirmDialog: boolean = false;

  ngOnInit(): void {
    this.data = data;
    this.editSettings = {
      allowEditing: true,
      allowAdding: true,
      allowDeleting: true,
      mode: 'Batch'
    };
    this.orderIDRules = { required: true, number: true };
    this.customerIDRules = { required: true };
    this.freightRules = { min: 1, max: 1000 };
  }
  toggleShowConfirmDialog(args:ChangeEventArgs): void {
    (this.grid as GridComponent).editSettings.showConfirmDialog = this.enableShowConfirmDialog;
  }
  toggleShowDeleteConfirmDialog(args:ChangeEventArgs): void {
    (this.grid as GridComponent).editSettings.showDeleteConfirmDialog = this.enableShowDeleteConfirmDialog;
  }
  
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

See also