---
title: "Row Dragging Between Grids"
framework: angular
version: "36.1.0"
---

# Row Dragging Between Grids

Row Drag Between Grids is concerned with seamless integration among different grids, allowing records to be dragged from one grid and dropped at a specific index on another grid.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `addRowDropZone` | `Function` |  |  | Adds a drop zone outside of the grid where rows can be dropped. Module: [`RowDragModule`](https://www.ag-grid.com/angular-data-grid/modules/). |
| `removeRowDropZone` | `Function` |  |  | Removes an external drop zone added by `addRowDropZone`. Module: [`RowDragModule`](https://www.ag-grid.com/angular-data-grid/modules/). |
| `getRowDropZoneParams` | `Function` |  |  | Returns the `RowDropZoneParams` to be used by another grid's `addRowDropZone` method. Module: [`RowDragModule`](https://www.ag-grid.com/angular-data-grid/modules/). |

## Adding a Grid as Target

To allow adding a grid as DropZone, the `getRowDropZoneParams` API method should be used in the target grid and the `addRowDropZone` in the source grid.

```js
const dropZoneParams = targetGridApi.getRowDropZoneParams({
    onDragStop: function() {
        alert('Record Dropped!');
    }
});

if (dropZoneParams) {
    sourceGridApi.addRowDropZone(dropZoneParams);

    // when the DropZone above is no longer needed
    sourceGridApi.removeRowDropZone(dropZoneParams);
}
```

In the example below, note the following:

- When you drag from one grid to another, a line will appear indicating where the row will be placed.
- Rows can be dragged from one grid to the other grid. When the row is received, it is **not** removed from the first grid. This is the choice of the example. The example could equally have removed from the other grid.
- Rows can be removed from both grids by dragging the row to the 'Trash' drop zone.
- New rows can be created by clicking on the red, green and blue buttons.

#### Two Grids with Drop Position

```ts
import { Component, ViewChild } from "@angular/core";

import { AgGridAngular } from "ag-grid-angular";
import type {
  ColDef,
  GetRowIdParams,
  GridApi,
  GridReadyEvent,
  RowDropZoneParams,
} from "ag-grid-community";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ModuleRegistry,
  RowApiModule,
  RowDragModule,
  RowStyleModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";

import "./styles.css";

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

ModuleRegistry.registerModules([
  RowDragModule,
  ClientSideRowModelApiModule,
  RowApiModule,
  TextFilterModule,
  RowStyleModule,
  ClientSideRowModelModule,
]);
@Component({
  standalone: true,
  imports: [AgGridAngular],
  selector: "my-app",
  template: `
    <div class="example-wrapper">
      <div class="inner-col">
        <div class="toolbar">
          <button
            class="factory factory-red"
            data-color="Red"
            data-side="left"
            (click)="onFactoryButtonClick($event)"
          >
            <i class="far fa-plus-square"></i>Add Red
          </button>
          <button
            class="factory factory-green"
            data-color="Green"
            data-side="left"
            (click)="onFactoryButtonClick($event)"
          >
            <i class="far fa-plus-square"></i>Add Green
          </button>
          <button
            class="factory factory-blue"
            data-color="Blue"
            data-side="left"
            (click)="onFactoryButtonClick($event)"
          >
            <i class="far fa-plus-square"></i>Add Blue
          </button>
        </div>
        <div style="height: 100%;" class="inner-col" #eLeftGrid>
          <ag-grid-angular
            style="height: 100%;"
            [defaultColDef]="defaultColDef"
            [getRowId]="getRowId"
            [rowClassRules]="rowClassRules"
            [rowDragManaged]="true"
            [suppressMoveWhenRowDragging]="true"
            [rowData]="leftRowData"
            [columnDefs]="columns"
            (gridReady)="onGridReady($event, 'Left')"
          />
        </div>
      </div>

      <div class="inner-col vertical-toolbar">
        <span class="bin" #eBin>
          <i class="far fa-trash-alt fa-3x" #eBinIcon></i>
        </span>
      </div>

      <div class="inner-col">
        <div class="toolbar">
          <button
            class="factory factory-red"
            data-color="Red"
            data-side="right"
            (click)="onFactoryButtonClick($event)"
          >
            <i class="far fa-plus-square"></i>Add Red
          </button>
          <button
            class="factory factory-green"
            data-color="Green"
            data-side="right"
            (click)="onFactoryButtonClick($event)"
          >
            <i class="far fa-plus-square"></i>Add Green
          </button>
          <button
            class="factory factory-blue"
            data-color="Blue"
            data-side="right"
            (click)="onFactoryButtonClick($event)"
          >
            <i class="far fa-plus-square"></i>Add Blue
          </button>
        </div>
        <div style="height: 100%;" class="inner-col" #eRightGrid>
          <ag-grid-angular
            style="height: 100%;"
            [defaultColDef]="defaultColDef"
            [getRowId]="getRowId"
            [rowClassRules]="rowClassRules"
            [rowDragManaged]="true"
            [suppressMoveWhenRowDragging]="true"
            [rowData]="rightRowData"
            [columnDefs]="columns"
            (gridReady)="onGridReady($event, 'Right')"
          />
        </div>
      </div>
    </div>
  `,
})
export class AppComponent {
  leftRowData: any[] = [];
  rightRowData: any[] = [];
  leftApi!: GridApi;
  rightApi!: GridApi;

  rowClassRules = {
    "red-row": 'data.color == "Red"',
    "green-row": 'data.color == "Green"',
    "blue-row": 'data.color == "Blue"',
  };

  defaultColDef: ColDef = {
    flex: 1,
    minWidth: 100,
    filter: true,
  };

  columns: ColDef[] = [
    { field: "id", rowDrag: true },
    { field: "color" },
    { field: "value1" },
    { field: "value2" },
  ];

  @ViewChild("eLeftGrid") eLeftGrid: any;
  @ViewChild("eRightGrid") eRightGrid: any;
  @ViewChild("eBin") eBin: any;
  @ViewChild("eBinIcon") eBinIcon: any;

  constructor() {
    this.leftRowData = createRowBlock(2);
    this.rightRowData = createRowBlock(2);
  }

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

  onGridReady(params: GridReadyEvent, side: string) {
    const api = params.api;
    if (side === "Left") {
      this.leftApi = api;
    } else {
      this.rightApi = api;
    }

    if (this.leftApi && this.rightApi) {
      this.addBinZone(this.leftApi);
      this.addBinZone(this.rightApi);
      this.addGridDropZone("Left", this.leftApi);
      this.addGridDropZone("Right", this.rightApi);
    }
  }

  addRecordToGrid(side: string, data: any) {
    // if data missing or data has no it, do nothing
    if (!data || data.id == null) {
      return;
    }

    const api = side === "left" ? this.leftApi : this.rightApi;
    // do nothing if row is already in the grid, otherwise we would have duplicates
    const rowAlreadyInGrid = !!api.getRowNode(data.id);

    if (rowAlreadyInGrid) {
      console.log("not adding row to avoid duplicates in the grid");
      return;
    }

    const transaction = {
      add: [data],
    };

    api.applyTransaction(transaction);
  }

  onFactoryButtonClick(e: any) {
    const button = e.currentTarget,
      buttonColor = button.getAttribute("data-color"),
      side = button.getAttribute("data-side"),
      data = createDataItem(buttonColor);

    this.addRecordToGrid(side, data);
  }

  binDrop(data: any) {
    // if data missing or data has no id, do nothing
    if (!data || data.id == null) {
      return;
    }

    const transaction = {
      remove: [data],
    };

    [this.leftApi, this.rightApi].forEach((api) => {
      const rowsInGrid = !!api.getRowNode(data.id);

      if (rowsInGrid) {
        api.applyTransaction(transaction);
      }
    });
  }

  addBinZone(api: GridApi) {
    const dropZone: RowDropZoneParams = {
      getContainer: () => this.eBinIcon.nativeElement,
      onDragEnter: () => {
        this.eBin.nativeElement.style.color = "blue";
        this.eBinIcon.nativeElement.style.transform = "scale(1.5)";
      },
      onDragLeave: () => {
        this.eBin.nativeElement.style = "";
        this.eBinIcon.nativeElement.style.transform = "scale(1)";
      },
      onDragStop: (params) => {
        this.binDrop(params.node.data);
        this.eBin.nativeElement.style = "";
        this.eBinIcon.nativeElement.style.transform = "scale(1)";
      },
    };

    api.addRowDropZone(dropZone);
  }

  addGridDropZone(side: string, api: GridApi) {
    const dropApi = side === "Left" ? this.rightApi : this.leftApi;
    const dropZone = dropApi.getRowDropZoneParams();

    api.addRowDropZone(dropZone!);
  }
}

let rowIdSequence = 100;

function createDataItem(color: string) {
  const obj = {
    id: rowIdSequence++,
    color: color,
    value1: Math.floor(window.agRandom() * 100),
    value2: Math.floor(window.agRandom() * 100),
  };

  return obj;
}

const createRowBlock = (blocks: any) =>
  Array.apply(null, Array(blocks || 1))
    .map(() => ["Red", "Green", "Blue"].map((color) => createDataItem(color)))
    .reduce((prev, curr) => prev.concat(curr), []);
```

[Live example: Two Grids with Drop Position](https://www.ag-grid.com/examples/row-dragging-to-grid/two-grids-with-drop-position/angular/)

## Dragging Multiple Records Between Grids

It is possible to drag multiple records at once from one grid to another.

In the example below, note the following:

- This example enables [Multi-Row Dragging](https://www.ag-grid.com/angular-data-grid/row-dragging-managed/#multi-row-dragging) between grids using `rowDragMultiRow`.
- When `Remove Source Rows` is selected, the rows will be removed from the **Athletes** grid once they are dropped onto the **Selected Athletes** grid.
- If `Only Deselect Source Rows` is selected, all selected rows that were copied will be deselected but will not be removed.
   Note: If some rows are selected and a row that isn't selected is copied, the selected rows will remain selected.
- If `None` is selected, the rows will be copied from one grid to another and the source grid will stay as is.

#### Multiple Records with Drop Position

```ts
import { HttpClient } from "@angular/common/http";
import { ChangeDetectionStrategy, Component, ViewChild } from "@angular/core";

import type { ICellRendererAngularComp } from "ag-grid-angular";
import { AgGridAngular } from "ag-grid-angular";
import type {
  ColDef,
  GetRowIdParams,
  GridApi,
  GridReadyEvent,
  ICellRendererParams,
  RowSelectionOptions,
} from "ag-grid-community";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ModuleRegistry,
  RowDragModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";

import "./styles.css";

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

ModuleRegistry.registerModules([
  RowDragModule,
  ClientSideRowModelApiModule,
  TextFilterModule,
  RowSelectionModule,
  ClientSideRowModelModule,
]);
@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: ` <i
    class="far fa-trash-alt"
    style="cursor: pointer"
    (click)="applyTransaction()"
  ></i>`,
})
export class SportRenderer implements ICellRendererAngularComp {
  private params!: ICellRendererParams;

  agInit(params: ICellRendererParams): void {
    this.params = params;
  }

  applyTransaction() {
    this.params.api.applyTransaction({ remove: [this.params.node.data] });
  }

  refresh() {
    return false;
  }
}

@Component({
  standalone: true,
  imports: [AgGridAngular],
  selector: "my-app",
  template: /*html */ ` <div class="top-container">
    <div class="example-toolbar panel panel-default">
      <div class="panel-body">
        <input type="radio" id="move" name="radio" checked #eMoveRadio />
        <label for="move">Remove Source Rows</label>
        <input type="radio" id="deselect" name="radio" #eDeselectRadio />
        <label for="deselect">Only Deselect Source Rows</label>
        <input type="radio" id="none" name="radio" />
        <label for="none">None</label>
        <span class="input-group-button">
          <button
            type="button"
            class="btn btn-default reset"
            style="margin-left: 5px;"
            (click)="reset()"
          >
            <i class="fas fa-redo" style="margin-right: 5px;"></i>Reset
          </button>
        </span>
      </div>
    </div>
    <div class="grid-wrapper">
      <div class="panel panel-primary" style="margin-right: 10px;">
        <div class="panel-heading">Athletes</div>
        <div class="panel-body">
          <div id="eLeftGrid">
            <ag-grid-angular
              style="height: 100%;"
              [defaultColDef]="defaultColDef"
              [rowSelection]="rowSelection"
              [rowDragMultiRow]="true"
              [getRowId]="getRowId"
              [rowDragManaged]="true"
              [suppressMoveWhenRowDragging]="true"
              [rowData]="leftRowData"
              [columnDefs]="leftColumns"
              (gridReady)="onGridReady($event, 0)"
            />
          </div>
        </div>
      </div>
      <div class="panel panel-primary" style="margin-left: 10px;">
        <div class="panel-heading">Selected Athletes</div>
        <div class="panel-body">
          <div id="eRightGrid">
            <ag-grid-angular
              style="height: 100%;"
              [defaultColDef]="defaultColDef"
              [getRowId]="getRowId"
              [rowDragManaged]="true"
              [rowData]="rightRowData"
              [columnDefs]="rightColumns"
              (gridReady)="onGridReady($event, 1)"
            />
          </div>
        </div>
      </div>
    </div>
  </div>`,
})
export class AppComponent {
  rawData: any[] = [];
  leftRowData: any[] = [];
  rightRowData: any[] = [];
  leftApi!: GridApi;
  rightApi!: GridApi;

  defaultColDef: ColDef = {
    flex: 1,
    minWidth: 100,
    filter: true,
  };

  rowSelection: RowSelectionOptions = {
    mode: "multiRow",
  };

  leftColumns: ColDef[] = [
    {
      rowDrag: true,
      maxWidth: 50,
      suppressHeaderMenuButton: true,
      suppressHeaderFilterButton: true,
      rowDragText: (params, dragItemCount) => {
        if (dragItemCount > 1) {
          return dragItemCount + " athletes";
        }
        return params.rowNode!.data.athlete;
      },
    },
    { field: "athlete" },
    { field: "sport" },
  ];

  rightColumns: ColDef[] = [
    {
      rowDrag: true,
      maxWidth: 50,
      suppressHeaderMenuButton: true,
      suppressHeaderFilterButton: true,
      rowDragText: (params, dragItemCount) => {
        if (dragItemCount > 1) {
          return dragItemCount + " athletes";
        }
        return params.rowNode!.data.athlete;
      },
    },
    { field: "athlete" },
    { field: "sport" },
    {
      suppressHeaderMenuButton: true,
      suppressHeaderFilterButton: true,
      maxWidth: 50,
      cellRenderer: SportRenderer,
    },
  ];

  @ViewChild("eLeftGrid") eLeftGrid: any;
  @ViewChild("eRightGrid") eRightGrid: any;
  @ViewChild("eMoveRadio") eMoveRadio: any;
  @ViewChild("eDeselectRadio") eDeselectRadio: any;

  constructor(private http: HttpClient) {
    this.http
      .get("https://www.ag-grid.com/example-assets/olympic-winners.json")
      .subscribe((data) => {
        const athletes: any[] = [];
        let i = 0;
        const dataArray = data as any[];

        while (athletes.length < 20 && i < dataArray.length) {
          var pos = i++;
          if (athletes.some((rec) => rec.athlete === dataArray[pos].athlete)) {
            continue;
          }
          athletes.push(dataArray[pos]);
        }
        this.rawData = athletes;
        this.loadGrids();
      });
  }

  loadGrids = () => {
    this.leftRowData = [...this.rawData];
    this.rightRowData = [];
  };

  reset = () => {
    this.eMoveRadio.nativeElement.checked = true;

    this.loadGrids();
  };

  getRowId = (params: GetRowIdParams) => params.data.athlete;

  onGridReady(params: GridReadyEvent, side: number) {
    if (side === 0) {
      this.leftApi = params.api;
    }

    if (side === 1) {
      this.rightApi = params.api;
      this.addGridDropZone();
    }
  }

  addGridDropZone() {
    const dropZoneParams = this.rightApi.getRowDropZoneParams({
      onDragStop: (params) => {
        const deselectCheck = this.eDeselectRadio.nativeElement.checked;
        const moveCheck = this.eMoveRadio.nativeElement.checked;
        const nodes = params.nodes;

        if (moveCheck) {
          this.leftApi.applyTransaction({
            remove: nodes.map(function (node) {
              return node.data;
            }),
          });
        } else if (deselectCheck) {
          this.leftApi.setNodesSelected({ nodes, newValue: false });
        }
      },
    });

    this.leftApi.addRowDropZone(dropZoneParams!);
  }
}
```

[Live example: Multiple Records with Drop Position](https://www.ag-grid.com/examples/row-dragging-to-grid/two-grids-with-multiple-records/angular/)
