Angular CDK Drag & Drop: Multi Direction Movement

Dharmen ShahDharmen Shah
4 min read

This article contains interactive demos, to see them in action visit it at https://angular-material.dev/articles/angular-cdk-drag-drop-mixed

Angular CDK Drag & Drop

The @angular/cdk/drag-drop module allows you to effortlessly and declaratively build drag-and-drop interfaces. It supports free dragging, list sorting, item transfers between lists, animations, touch devices, custom drag handles, previews, placeholders, horizontal lists, and axis locking.

Reordering lists

Wrapping a set of cdkDrag elements with cdkDropList groups them into a reorderable collection. As elements move, they will automatically rearrange. However, this won't update your data model; you can listen to the cdkDropListDropped event to update the data model after the user finishes dragging.

<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
    @for (movie of movies; track movie) {
      <div class="example-box" cdkDrag>{{movie}}</div>
    }
</div>
import { Component } from "@angular/core";
import {
    CdkDrag,
    CdkDragDrop,
    CdkDropList,
    moveItemInArray,
} from "@angular/cdk/drag-drop";

@Component({
    selector: "app-basic-drag-drop",
    templateUrl: "basic.component.html",
    styleUrl: "basic.component.css",
    standalone: true,
    imports: [CdkDropList, CdkDrag],
})
export class BasicDragDropExampleComponent {
    movies = [
        "Episode I - The Phantom Menace",
        "Episode II - Attack of the Clones",
        "Episode III - Revenge of the Sith",
        "Episode IV - A New Hope",
        "Episode V - The Empire Strikes Back",
        "Episode VI - Return of the Jedi",
        "Episode VII - The Force Awakens",
        "Episode VIII - The Last Jedi",
        "Episode IX - The Rise of Skywalker",
    ];

    drop(event: CdkDragDrop<string[]>) {
        moveItemInArray(
        this.movies,
        event.previousIndex,
        event.currentIndex
      );
    }
}
.example-list {
    width: 500px;
    max-width: 100%;
    border: solid 1px #ccc;
    min-height: 60px;
    display: block;
    background: white;
    border-radius: 4px;
    overflow: hidden;
  }

  .example-box {
    padding: 20px 10px;
    border-bottom: solid 1px #ccc;
    color: rgba(0, 0, 0, 0.87);
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
    cursor: move;
    background: white;
    font-size: 14px;
  }

  .cdk-drag-preview {
    border: none;
    box-sizing: border-box;
    border-radius: 4px;
    box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
                0 8px 10px 1px rgba(0, 0, 0, 0.14),
                0 3px 14px 2px rgba(0, 0, 0, 0.12);
  }

  .cdk-drag-placeholder {
    opacity: 0;
  }

  .cdk-drag-animating {
    transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
  }

  .example-box:last-child {
    border: none;
  }

  .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
    transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
  }

Image description

Orientations

Angular CDK supports 2 orientations for reordering lists: vertical (default) and horizontal.

The cdkDropList directive assumes that lists are vertical by default. This can be changed by setting the cdkDropListOrientation property to horizontal.

<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)">
  @for (timePeriod of timePeriods; track timePeriod) {
    <div class="example-box" cdkDrag>{{timePeriod}}</div>
  }
</div>
import {Component} from '@angular/core';
import {CdkDragDrop, CdkDrag, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-horizontal-drag-drop',
  templateUrl: 'horizontal.component.html',
  styleUrl: 'horizontal.component.css',
  standalone: true,
  imports: [CdkDropList, CdkDrag],
})
export class HorizontalDragDropExampleComponent {
  timePeriods = [
    'Bronze age',
    'Iron age',
    'Middle ages',
    'Early modern period',
    'Long nineteenth century',
  ];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
  }
}
.example-list {
  width: 54vw;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: flex;
  flex-direction: row;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-right: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  flex-grow: 1;
  flex-basis: 0;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

Image description

Mixed Orientation

By default, cdkDropList sorts items by moving them with a CSS transform. This enables animated sorting for a better user experience, but it only works in one direction: either vertically or horizontally.

From Angular Material v18.1.0, for sortable lists that need to wrap, you can set cdkDropListOrientation="mixed". This uses a different sorting strategy by moving elements in the DOM, allowing items to wrap to the next line. However, it cannot animate the sorting action.

<div cdkDropList cdkDropListOrientation="mixed" class="example-list" (cdkDropListDropped)="drop($event)">
  @for (item of items; track item) {
    <div class="example-box" cdkDrag>{{item}}</div>
  }
</div>
import {Component} from '@angular/core';
import {CdkDragDrop, CdkDrag, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';

/**
 * @title Drag&Drop horizontal wrapping list
 */
@Component({
  selector: 'app-mixed-drag-drop',
  templateUrl: 'mixed.component.html',
  styleUrl: 'mixed.component.css',
  standalone: true,
  imports: [CdkDropList, CdkDrag],
})
export class MixedDragDropExampleComponent {
  items = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.items, event.previousIndex, event.currentIndex);
  }
}
.example-list {
  display: flex;
  flex-wrap: wrap;
  width: 505px;
  max-width: 100%;
  gap: 15px;
  padding: 15px;
  border: solid 1px #ccc;
  min-height: 60px;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border: solid 1px #ccc;
  border-radius: 4px;
  color: rgba(0, 0, 0, 0.87);
  display: inline-block;
  box-sizing: border-box;
  cursor: move;
  background: white;
  text-align: center;
  font-size: 14px;
  min-width: 115px;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
              0 8px 10px 1px rgba(0, 0, 0, 0.14),
              0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

Image description

10
Subscribe to my newsletter

Read articles from Dharmen Shah directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dharmen Shah
Dharmen Shah

I am a ๐Ÿ‘”๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Front-end Developer. I like to work on ๐Ÿ’ป Web stuff (HTML, CSS, JS), ๐Ÿ…ฐ๏ธ Angular, โš›๏ธ React, ๐Ÿ–Œ๏ธ Bootstrap. I also love ๐Ÿค— to contribute to ๐Ÿ‘ Open-Source Projects and sometime โœ’๏ธ write ๐Ÿ“œ articles.