Angular Data Grid Row Reordering

Row reordering

Let users drag rows into a new order with a per-row grip — the Angular Data Grid hands you the reordered array and never mutates your data.

Some lists have an order that only a person can decide: a task backlog, a playlist, the steps of a workflow. rowOrder gives each row a grip, and the grid tells you where the row was dropped — you own the data, so you own the new order.

Row reordering

[rowOrder]="true" adds a narrow handle column before the data columns. Only the grip starts a drag: a row is full of interactive content — checkboxes, editors, custom cells — that a whole-row handle would swallow.

Task
Owner
Status
Draft the release notes Alice In progress
Review the API surface Bob Blocked
Ship the beta Carol Todo
Update the changelog Dave Todo
ts
import { Component, signal } from '@angular/core'
import { DataGridComponent } from '@coreui/angular-data-grid'
import type { DataGridColumn, DataGridItem, DataGridRowOrderChangeEvent } from '@coreui/angular-data-grid'

@Component({
  selector: 'docs-data-grid-row-order-example',
  imports: [DataGridComponent],
  template: `
    <c-data-grid
      [columns]="columns"
      [items]="items()"
      [itemKey]="itemKey"
      [rowOrder]="true"
      [sorting]="false"
      [virtualization]="false"
      (rowOrderChange)="onRowOrderChange($event)"
    />
  `
})
export class DataGridRowOrderExample {
  readonly columns: DataGridColumn[] = [
    { key: 'task', label: 'Task' },
    { key: 'owner', label: 'Owner' },
    { key: 'status', label: 'Status' }
  ]
readonly items = signal<DataGridItem[]>([
    {
 id: 1, task: 'Draft the release notes', owner: 'Alice', status: 'In progress'
},
    {
 id: 2, task: 'Review the API surface', owner: 'Bob', status: 'Blocked'
},
    {
 id: 3, task: 'Ship the beta', owner: 'Carol', status: 'Todo'
},
    {
 id: 4, task: 'Update the changelog', owner: 'Dave', status: 'Todo'
}
  ])
readonly itemKey = (item: DataGridItem) => String(item.id)

  onRowOrderChange(event: DataGridRowOrderChangeEvent) {
    this.items.set(event.items)
  }
}

The contract

The grid never mutates items. Dropping a row fires (rowOrderChange) with everything needed to apply the move — the same contract inline editing uses:

PayloadDescription
itemsThe full array in its new order, ready to assign or send to your API.
itemThe row that moved.
oldIndexIts index in items before the move.
newIndexIts index after it.

Until you apply it, nothing changes on screen. That is deliberate: the order is usually persisted, and a row that snaps back when the request fails is better than one that lies.

The indexes are positions in items, not in the rendered window — in pagination mode the page offset is already added, so dragging the first row of page 2 reports oldIndex: 10, not 0.

The drag interaction

Row dragging is the same pointer-based controller as column dragging, turned on its side: a ghost with the row’s first cell, a live preview where the rows it passes slide aside and it slides into the gap, edge auto-scroll for long lists, and Escape to cancel. It works the same with a mouse, a pen and a finger, and honours prefers-reduced-motion.

Options

OptionTypeDefaultDescription
rowOrderbooleanfalseAdds a drag handle per row for reordering.
rowHandleIconTemplateRefgripCustom handle icon replacing the default one.

The handle’s accessible name comes from labels.reorderRow — see localization.