---
product: "AG Grid"
title: "Unmanaged Row Dragging"
description: "Unmanaged dragging is the default dragging for the grid. To use it, do not set the property rowDragManaged ."
framework: angular
version: "36.2.0"
related:
    - title: "Managed Row Dragging"
      url: "https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-managed/"
    - title: "Row Dragging Customisation"
      url: "https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-customisation/"
    - title: "External DropZone"
      url: "https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-to-external-dropzone/"
    - title: "Grid to Grid"
      url: "https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-to-grid/"
llms: "https://www.ag-grid.com/archive/36.2.0/llms.txt"
---

# Unmanaged Row Dragging

Unmanaged dragging is the default dragging for the grid. To use it, do not set the property `rowDragManaged`.

#### Row Drag Simple Unmanaged

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  ColumnApiModule,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberFilterModule,
  RowDragModule,
  RowDragMoveEvent,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { getData } from "./data";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowDragModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
  ColumnApiModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [columnDefs]="columnDefs"
    [defaultColDef]="defaultColDef"
    [getRowId]="getRowId"
    [rowData]="rowData"
    (sortChanged)="onSortChanged($event)"
    (filterChanged)="onFilterChanged($event)"
    (rowDragMove)="onRowDragMove($event)"
    (gridReady)="onGridReady($event)"
  /> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    { field: "athlete", rowDrag: true },
    { field: "country" },
    { field: "year", width: 100 },
    { field: "date" },
    { field: "sport" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
  ];
  defaultColDef: ColDef = {
    width: 170,
    filter: true,
  };
  rowData!: any[];

  // listen for change on sort changed
  onSortChanged() {
    const colState = this.gridApi.getColumnState() || [];
    sortActive = colState.some((c) => c.sort);
    // suppress row drag if either sort or filter is active
    const suppressRowDrag = sortActive || filterActive;
    console.log(
      "sortActive = " +
        sortActive +
        ", filterActive = " +
        filterActive +
        ", suppressRowDrag = " +
        suppressRowDrag,
    );
    this.gridApi.setGridOption("suppressRowDrag", suppressRowDrag);
  }

  // listen for changes on filter changed
  onFilterChanged() {
    filterActive = this.gridApi.isAnyFilterPresent();
    // suppress row drag if either sort or filter is active
    const suppressRowDrag = sortActive || filterActive;
    console.log(
      "sortActive = " +
        sortActive +
        ", filterActive = " +
        filterActive +
        ", suppressRowDrag = " +
        suppressRowDrag,
    );
    this.gridApi.setGridOption("suppressRowDrag", suppressRowDrag);
  }

  onRowDragMove(event: RowDragMoveEvent) {
    const movingNode = event.node;
    const overNode = event.overNode;
    const rowNeedsToMove = movingNode !== overNode;
    if (rowNeedsToMove) {
      // the list of rows we have is data, not row nodes, so extract the data
      const movingData = movingNode.data;
      const overData = overNode!.data;
      const fromIndex = immutableStore.indexOf(movingData);
      const toIndex = immutableStore.indexOf(overData);
      const newStore = immutableStore.slice();
      moveInArray(newStore, fromIndex, toIndex);
      immutableStore = newStore;
      this.gridApi.setGridOption("rowData", newStore);
      this.gridApi.clearFocusedCell();
    }
    function moveInArray(arr: any[], fromIndex: number, toIndex: number) {
      const element = arr[fromIndex];
      arr.splice(fromIndex, 1);
      arr.splice(toIndex, 0, element);
    }
  }

  onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;

    // add id to each item, needed for immutable store to work
    immutableStore.forEach(function (data, index) {
      data.id = index;
    });
    params.api.setGridOption("rowData", immutableStore);
  }

  getRowId = (params: GetRowIdParams) => {
    return String(params.data.id);
  };
}

let immutableStore: any[] = getData();
let sortActive = false;
let filterActive = false;
```

[Live example: Row Drag Simple Unmanaged](https://www.ag-grid.com/archive/36.2.0/examples/row-dragging-unmanaged/simple-unmanaged/angular/)

The example above shows how to implement simple row dragging using unmanaged row dragging and events similarly to the [Managed Row Dragging](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-managed/) example; however, the logic for moving the rows is in the application rather than the grid.

The property `suppressRowDrag=true` is set by the application depending on whether sorting or filtering is active. This is because the logic in the example doesn't cover these scenarios and wants to prevent row dragging when sorting or filtering is active.

## Differences from Managed Row Dragging

Unmanaged dragging differs from managed dragging in the following ways:

- The grid does not manage moving of the rows. The only thing the grid responds with is firing drag events. It is up to the application to do the moving of the rows (if that is what the application wants to do).
- Dragging is allowed while sort is applied.
- Dragging is allowed while filter is applied.
- Dragging is allowed while row group or pivot is applied.

> **Note**
>
> It is not possible for the grid to provide a generic solution for row dragging that fits all usage scenarios. Unmanaged row dragging is provided as a way for the application developer to meet their requirements by responding to events emitted by the grid.

## Row Drag Events

Row drag events are raised by both [Managed Row Dragging](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-managed/) and unmanaged row dragging.

There are five grid events associated with row dragging which are:

- `rowDragEnter`: A drag has started, or dragging already started and the mouse has re-entered the grid having previously left the grid.
- `rowDragMove`: The mouse has moved while dragging.
- `rowDragLeave`: The mouse has left the grid while dragging.
- `rowDragEnd`: The drag has finished over the grid.
- `rowDragCancel`: The drag has cancelled over the grid.

Typically a drag will fire the following events:

1. `rowDragEnter` fired once - The drag has started.
2. `rowDragMove` fired multiple times - The mouse is dragging over the rows.
3. `rowDragEnd` fired once - The drag has finished.

Additional `rowDragLeave` and `rowDragEnter` events are fired if the mouse leaves or re-enters the grid. If the drag is finished outside of the grid, then the `rowDragLeave` is the last event fired and no `rowDragEnd` is fired, as the drag did not end on the grid.

> **Note**
>
> When the Grid is created, a [Drop Zone](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-to-external-dropzone/) that is responsible for firing all the Row Drag Events is added to the Grid Body. This why Row Drag Events (including `rowDragEnd`) are only fired when they happen on top of the Grid. If you need to monitor when a Row Drag ends outside of the Grid, for example, use the [DragStopped](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/grid-events/#reference-dragAndDrop-dragStopped) event.

Each of the five row drag events extend the `RowDragEvent` interface.

Properties available on the `RowDragEvent&lt;TData = any, TContext = any, T extends RowDragEventType = RowDragEventType&gt;` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `node` | `IRowNode` |  |  |  |
| `nodes` | `IRowNode[]` |  |  |  |
| `event` | `MouseEvent` |  |  |  |
| `eventPath` | `EventTarget[]` |  |  |  |
| `vDirection` | `'up' \| 'down' \| null` |  |  |  |
| `overIndex` | `number` |  |  |  |
| `overNode` | `IRowNode` |  |  |  |
| `y` | `number` |  |  |  |
| `rowsDrop` | `RowsDropParams \| null` |  |  |  |
| `api` | `GridApi` |  |  |  |
| `context` | `TContext` |  |  |  |
| `type` | `TEventType` |  |  |  |

## Example Events

The following example demonstrates unmanaged row dragging with no attempt by the application or the grid to re-order the rows. This is on purpose to demonstrate that the grid will not attempt to re-order rows unless the `rowDragManaged` property is set to true. The example also demonstrates all the events that are fired.

From the example the following can be noted:

- The first column has `rowDrag=true` which results in a draggable area included in the cell.
- The grid has not set `rowDragManaged` which results in the grid not reordering rows as they are dragged.
- All of the drag events are listened for and when one is received, it is printed to the console. To best see this, open the example in a new tab and open the developer console.
- Because `rowDragManaged` is not set, the row dragging is left enabled even if sorting or filtering is applied. This is because your application should decide if dragging should be allowed or suppressed using the `suppressRowDrag` property.
- While dragging the row, the `setRowDropPositionIndicator` API method is called to display the projected row drop location using a horizontal line indicator.

#### Row Drag Events

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  NumberFilterModule,
  RowDragCancelEvent,
  RowDragEndEvent,
  RowDragEnterEvent,
  RowDragLeaveEvent,
  RowDragModule,
  RowDragMoveEvent,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowDragModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);
import { IOlympicData } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div class="example-header" style="background-color: #ccaa22a9">
      Rows in this example do not move, only events are fired
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [rowData]="rowData"
      (rowDragEnter)="onRowDragEnter($event)"
      (rowDragEnd)="onRowDragEnd($event)"
      (rowDragMove)="onRowDragMove($event)"
      (rowDragLeave)="onRowDragLeave($event)"
      (rowDragCancel)="onRowDragCancel($event)"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  columnDefs: ColDef[] = [
    { field: "athlete", rowDrag: true },
    { field: "country" },
    { field: "year", width: 100 },
    { field: "date" },
    { field: "sport" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
  ];
  defaultColDef: ColDef = {
    width: 170,
    filter: true,
  };
  rowData!: IOlympicData[];

  constructor(private http: HttpClient) {}

  onRowDragEnter(e: RowDragEnterEvent) {
    console.log("onRowDragEnter: node", e.node.id);
  }

  onRowDragEnd(e: RowDragEndEvent) {
    console.log("onRowDragEnd: node", e.node.id);
    e.api.setRowDropPositionIndicator(null);
  }

  onRowDragMove(e: RowDragMoveEvent) {
    console.log("onRowDragMove: node", e.node.id);
    const overNodeTop = e.overNode?.rowTop ?? 0;
    const overNodeHeight = e.overNode?.rowHeight ?? 0;
    // yRatio is 0 if the mouse is in the center of the row, less than -0.5 if above, greater than 0.5 if below
    const yRatio = (e.y - overNodeTop - overNodeHeight / 2) / overNodeHeight;
    e.api.setRowDropPositionIndicator({
      row: e.overNode,
      dropIndicatorPosition: yRatio < 0 ? "above" : "below",
    });
  }

  onRowDragLeave(e: RowDragLeaveEvent) {
    console.log("onRowDragLeave: node", e.node.id);
    e.api.setRowDropPositionIndicator(null);
  }

  onRowDragCancel(e: RowDragCancelEvent) {
    console.log("onRowDragCancel: node", e.node.id);
    e.api.setRowDropPositionIndicator(null);
  }

  onGridReady(params: GridReadyEvent<IOlympicData>) {
    this.http
      .get<
        IOlympicData[]
      >("https://www.ag-grid.com/example-assets/olympic-winners.json")
      .subscribe((data) => (this.rowData = data));
  }
}
```

[Live example: Row Drag Events](https://www.ag-grid.com/archive/36.2.0/examples/row-dragging-unmanaged/dragging-events/angular/)

> **Note**
>
> When dragging Multiple Rows with unmanaged row dragging, the application is in control of what gets dragged. It is possible to use the events to drag more than one row at a time, e.g. to move all selected rows in one go if using row selection.

See also [Preventing Dropping on Certain Rows](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/row-dragging-managed/#preventing-dropping-on-certain-rows).

> **Note**
>
> For grouping-specific guidance, including managed row dragging support, see [Row Dragging with Row Groups](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/grouping-row-dragging/).

## Other Row Models

Unmanaged row dragging will work with any of the row models - [Infinite](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/infinite-scrolling/), [Server-Side](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/server-side-model/) and [Viewport](https://www.ag-grid.com/archive/36.2.0/angular-data-grid/viewport/). With unmanaged dragging, the implementation of what happens when a particular drag happens is up to your application.
