DEV Community

Cover image for Angular CDK Drag & Drop: Multi Direction Movement
Dharmen Shah for Angular Material Dev

Posted on • Originally published at angular-material.dev

12 1 1 1 1

Angular CDK Drag & Drop: Multi Direction Movement

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>


Enter fullscreen mode Exit fullscreen mode


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
      );
    }
}



Enter fullscreen mode Exit fullscreen mode


.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);
  }



Enter fullscreen mode Exit fullscreen mode

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>



Enter fullscreen mode Exit fullscreen mode


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);
  }
}



Enter fullscreen mode Exit fullscreen mode


.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);
}



Enter fullscreen mode Exit fullscreen mode

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>



Enter fullscreen mode Exit fullscreen mode


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);
  }
}



Enter fullscreen mode Exit fullscreen mode


.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);
}



Enter fullscreen mode Exit fullscreen mode

Image description

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Dharmen Shah,
Top, very nice and helpful !
Thanks for sharing.

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more