> For AI agents: the complete documentation index is available at https://gultyayev.github.io/ngx-virtual-dnd/llms.txt, the full documentation bundle is available at https://gultyayev.github.io/ngx-virtual-dnd/llms-full.txt.

# Handling drops

The library never mutates your data. When an item is dropped, the destination droppable emits a `DropEvent`, and you apply the move.

## The drop event

```ts
interface DropEvent {
  source: {
    draggableId: string;
    droppableId: string;
    index: number; // where the item was
    data?: unknown; // vdndDraggableData
  };
  destination: {
    droppableId: string;
    placeholderId: string; // deprecated: always END_OF_LIST; use index for the position
    index: number; // where the item goes, after it is removed from the source
    data?: unknown; // vdndDroppableData (droppableData on vdnd-sortable-list)
  };
}
```

`destination.index` is the insertion index **after** the item is removed from its source. "Remove at `source.index`, then insert at `destination.index`" is always correct, whether the move stays in one list or crosses lists.

With virtual scrolling most items are not in the DOM, so always work from the event's indexes, never from DOM order.

## Helpers

| Function                      | Use when                                                                       |
| ----------------------------- | ------------------------------------------------------------------------------ |
| `moveItem(event, lists)`      | Your lists are `WritableSignal<T[]>`. Works within one list and across lists.  |
| `reorderItems(event, list)`   | A single signal-based list. Reorders only.                                     |
| `applyMove(event, lists)`     | Immutable state (a store, a service). Takes plain arrays and returns new ones. |
| `isNoOpDrop(event)`           | `true` when the item was dropped back where it started.                        |
| `insertAt(list, item, index)` | Returns a new array with the item inserted.                                    |
| `removeAt(list, index)`       | Returns a new array without the item at `index`.                               |

### Signals

```ts
onDrop(event: DropEvent): void {
  moveItem(event, { todo: this.todo, done: this.done });
}
```

### Stores and immutable state

```ts
onDrop(event: DropEvent): void {
  if (isNoOpDrop(event)) return;

  const lists = applyMove(event, {
    todo: this.store.todo(),
    done: this.store.done(),
  });
  this.store.setLists(lists);
}
```

`applyMove()` returns a shallow copy of the record, with new arrays for the lists that changed.

### Saving to a server

Apply the move straight away and roll back if the request fails:

```ts
onDrop(event: DropEvent): void {
  if (isNoOpDrop(event)) return;

  const previous = { todo: this.todo(), done: this.done() };
  moveItem(event, { todo: this.todo, done: this.done });

  this.api
    .moveTask(event.source.draggableId, event.destination.droppableId, event.destination.index)
    .subscribe({
      error: () => {
        this.todo.set(previous.todo);
        this.done.set(previous.done);
      },
    });
}
```

## Drag lifecycle events

Draggables also emit `(dragStart)` and `(dragEnd)`:

```angular-html
<div [vdndDraggable]="task.id" (dragStart)="onDragStart($event)" (dragEnd)="onDragEnd($event)">
  {{ task.title }}
</div>
```

`(dragEnd)`
 fires after every drag, dropped or not. Check `event.destinationIndex === null`
 to detect "no drop": the drag was cancelled (for example with Escape
), or released outside every droppable or over a disabled one. Don't rely on `cancelled`
, which is `true`
 only in the first case. [`DragEndEvent`](https://gultyayev.github.io/ngx-virtual-dnd/api/events.md#dragendevent)
 lists every way a drag can end without a drop.
During a drag, droppables and `<vdnd-sortable-list>` emit `(placeholderMove)` each time the placeholder moves within them. Use it for per-step feedback such as haptics. See [Animations and haptics](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/animations.md#placeholder-move-events).

To react to drags anywhere in the app, for example to show a hint, inject `DragStateService` and read its signals, such as `isDragging()` and `activeDroppableId()`. See the [API reference](https://gultyayev.github.io/ngx-virtual-dnd/api/services.md).
