DataGrid batch editing

Update several existing records together. Paste values into a selected range, review the drafts, then apply or discard the changes.

Enable batch editing when users need to update quantities, dates or other fields across multiple rows. The grid stages pasted values separately from committed data and validates the whole batch before applying it.

Example

The following example demonstrates updating dispatch quantities for several orders. Select the first Quantity cell, then paste two lines such as 12 and 18. Drafts have a dashed border. Apply commits both rows as one Undo step.

Pending cells: 0 · Undo: 0 · Redo: 0

Select a Quantity cell to begin.

No batch events yet.

Clipboard fallback and validation

If native clipboard access is unavailable, select a target cell and paste text here. Quantity must be between 1 and 100.

Enable batch editing

Add ngbGridBatchEditing, enable range selection and editing, and supply a stable, unique string or number trackBy key. Mark protected columns with editable: false.

The following standalone example includes Apply, Discard and optional history controls. Customize their labels, icons and order in the existing grid toolbar.

      import { Component, signal } from '@angular/core';
import {
  ColumnDef, Datagrid, NgbDatagridToolbarComponent,
  NgbGridBatchEditingDirective, NgbGridBatchApplyToolDirective,
  NgbGridBatchDiscardToolDirective, NgbDataGridHistoryDirective,
  NgbGridUndoToolDirective, NgbGridRedoToolDirective,
  NgbGridBatchSaveEvent, NgbGridBatchRowChange, NgbGridBatchCellError,
} from '@angular-bootstrap/ngbootstrap';

interface Order { id: string; quantity: number; }

@Component({
  selector: 'app-order-batch',
  standalone: true,
  imports: [Datagrid, NgbDatagridToolbarComponent,
    NgbGridBatchEditingDirective, NgbGridBatchApplyToolDirective,
    NgbGridBatchDiscardToolDirective, NgbDataGridHistoryDirective,
    NgbGridUndoToolDirective, NgbGridRedoToolDirective],
  template: `
    <ngb-datagrid #grid ngbGridHistory ngbGridBatchEditing
      #batch="ngbGridBatchEditing" cellSelection="range"
      [enableEdit]="true" [trackBy]="rowKey"
      [columns]="columns" [data]="rows" [batchValidator]="validate"
      (batchSave)="onSave($event)">
      <ngb-datagrid-toolbar [grid]="grid" ariaLabel="Order editing">
        <button ngbGridBatchApplyTool>Apply changes</button>
        <button ngbGridBatchDiscardTool>Discard</button>
        <button ngbGridUndoTool>Undo</button>
        <button ngbGridRedoTool>Redo</button>
      </ngb-datagrid-toolbar>
    </ngb-datagrid>
    <p role="status">{{ batch.message() || saved() }}</p>
    @for (error of batch.errors(); track $index) {
      <p>{{ error.rowId }} / {{ error.field }}: {{ error.message }}</p>
    }
  `,
})
export class OrderBatchComponent {
  readonly saved = signal('');
  readonly rowKey = (_index: number, row: Order) => row.id;
  rows: Order[] = [
    { id: 'ORD-4201', quantity: 4 },
    { id: 'ORD-4202', quantity: 6 },
  ];
  readonly columns: ColumnDef<Order>[] = [
    { field: 'id', header: 'Order', editable: false },
    { field: 'quantity', header: 'Quantity', type: 'number', required: true },
  ];
  readonly validate = (
    changes: readonly NgbGridBatchRowChange<Order>[],
    _signal: AbortSignal,
  ): NgbGridBatchCellError[] => changes
    .filter(change => change.updated.quantity < 1 || change.updated.quantity > 100)
    .map(change => ({ rowId: change.rowId, field: 'quantity',
      message: 'Use a quantity from 1 to 100.' }));

  onSave(event: NgbGridBatchSaveEvent<Order>): void {
    const updated = new Map(event.changes.map(change => [change.rowId, change.updated]));
    this.rows = this.rows.map(row => updated.get(row.id) ?? row);
    this.saved.set(event.changes.length + ' rows saved locally.');
    // Send event.changes to your backend here, including undo/redo events.
    // This example has no server persistence.
  }
}
    

A paste starts at the upper-left cell of the selected range and targets existing editable cells on the current page. It does not insert rows or repeat values to fill a larger selection.

Validate and save changes

Apply checks column types and required fields, then runs your optional batchValidator. Return cell errors, or a Promise of errors, to prevent the entire batch from committing. Invalid drafts remain visible for correction; Discard clears them without changing saved values.

For asynchronous validation, use the supplied AbortSignal to cancel requests when drafts are discarded. Show batch.errors() in a visible summary and batch.message() in a status region.

APIBehavior
cellSelection / cellRangeChangeEnable range mode; receive anchor/focus row IDs and column fields. Selection defaults to none.
stagePaste(text) / copySelection()Return a typed result. Copy includes text on success; staging reports changedCells.
applyBatch() / discardBatch()Apply returns a Promise of a result. Discard clears drafts and cancels validation.
batchValidator(changes, signal)Return cell errors or a Promise. Honor cancellation; current column validation also runs.
pendingCount / validating / errors / message / canApplyRead-only signals for custom controls and feedback.
clipboardParse / clipboardFormatColumn callbacks for typed input and output; receive detached values/rows.
undoAsync() / redoAsync() / busy()Replay batch history with validation. Built-in toolbar tools and shortcuts handle this automatically.
batchSaveOne event with changes (rowId, original, updated) and optional historyAction (undo or redo).

Persist batchSave.changes as one backend transaction. This demo reports events locally; it does not contact a server. Replayed batches use the same event. No per-row rowSave or dataStateChange is emitted.

Apply updates the grid’s local data before batchSave fires. Update your bound array from the event as shown above; it does not wait for a server response. Your app must prevent further editing while a save is pending and handle authorization and failure recovery. If saving fails, reload authoritative data and clear history before allowing more edits.

Undo and redo

Add ngbGridHistory to record each successful Apply as one history step. Undo and Redo validate and restore the whole batch together, and emit one batchSave event with historyAction set to undo or redo. Save these events through the same persistence handler.

Toolbar tools and keyboard shortcuts handle asynchronous replay automatically. For custom application controls, use await history.undoAsync() or redoAsync(). Conflicts or validation errors leave all rows and the history step unchanged.

Keyboard and accessibility

Use arrows to move, Shift+arrows to extend the range, Home/End within the row and Escape to clear selection. Ctrl/Cmd+C and V copy/paste. Ctrl/Cmd+Z undoes; Ctrl/Cmd+Shift+Z or Ctrl+Y redoes. Tab reaches toolbar buttons; Enter/Space activates them.

Native editors retain text shortcuts. Selection uses aria-selected, errors use aria-invalid, and the grid announces status. Save/cancel an editor or apply/discard drafts before replay. Async replay disables history tools. Provide visible labels and a validation summary when building custom controls. Touch-drag range selection is not supported.

Limitations

  • One rectangular range on the current page; no formulas, new rows or cross-page paste. Use pagination for large datasets; this feature does not virtualize rows.
  • Requires the default edit service and a regular table without grouping, stacked cards, sticky/detail rows or row reordering.
  • Paste accepts up to 10,000 cells and 1 MiB of text. Dates use ISO text unless you supply a conversion callback. Custom cell templates should render the proposed $implicit value.
  • After data or configuration changes, discard stale drafts and paste again. Drafts and ranges are excluded from saved views and history.
  • Batch history is bounded by the step limit, 50,000 changed cells and 10 MiB of estimated snapshot payload. An oversized batch saves but clears history with a message.

This project is not affiliated with ng-bootstrap or ngx-bootstrap. Those projects focus mainly on Bootstrap components for Angular. ngbootstrap focuses on Angular UI for data-heavy apps, especially Data Grid, Angular-native Form Builder, drag and drop workflows, documentation examples, and performance-focused Angular patterns.