Visión General
Esta página describe las directivas de drag and drop que te permiten crear rápidamente interfaces de drag and drop con lo siguiente:
- Arrastre libre
- Crear una lista de elementos arrastrables reordenables
- Transferir elementos arrastrables entre listas
- Animaciones de arrastre
- Bloquear elementos arrastrables a lo largo de un eje o elemento
- Añadir manejadores de arrastre personalizados
- Añadir vistas previas al arrastrar
- Añadir marcadores de posición (placeholders) de arrastre personalizados
Para la referencia completa de la API, consulta la página de referencia de la API de drag and drop del Angular CDK.
Antes de comenzar
Instalación del CDK
El Component Dev Kit (CDK) es un conjunto de primitivas de comportamiento para construir componentes. Para usar las directivas de drag and drop, primero instala @angular/cdk desde npm. Puedes hacerlo desde tu terminal usando Angular CLI:
ng add @angular/cdk
Importando drag and drop
Para usar drag and drop, importa lo que necesites de las directivas en tu componente.
import {Component} from '@angular/core';import {CdkDrag} from '@angular/cdk/drag-drop';@Component({ selector: 'my-custom-component', templateUrl: 'my-custom-component.html', standalone: true, imports: [CdkDrag],})export class DragDropExample {}
Crear elementos arrastrables
Puedes hacer que cualquier elemento sea arrastrable agregando la directiva cdkDrag. Por defecto, todos los elementos arrastrables soporta arrastre libre.
app/app.component.html
<div class="example-box" cdkDrag> Drag me around</div>
app/app.component.ts
import {CdkDrag} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Basic Drag&Drop */@Component({ selector: 'cdk-drag-drop-overview-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag],})export class CdkDragDropOverviewExample {}
app/app.component.css
.example-box { width: 200px; height: 200px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); cursor: move; display: flex; justify-content: center; align-items: center; text-align: center; background: #fff; border-radius: 4px; position: relative; z-index: 1; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-box:active { 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);}
Crear una lista de elementos arrastrables reordenables
Añade la directiva cdkDropList a un elemento padre para agrupar elementos arrastrables en una colección reordenable. Esto define dónde se pueden soltar los elementos arrastrables. Los elementos arrastrables en el grupo de la lista (drop list) se reorganizan automáticamente cuando un elemento se mueve.
Las directivas de drag and drop no actualizan tu modelo de datos. Para actualizar el modelo de datos, escucha el evento cdkDropListDropped (una vez que el usuario termina de arrastrar) y actualiza el modelo de datos manualmente.
app/app.component.html
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)"> @for (movie of movies; track movie) { <div class="example-box" cdkDrag>{{movie}}</div> }</div>
app/app.component.ts
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop sorting */@Component({ selector: 'cdk-drag-drop-sorting-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropSortingExample { 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); }}
app/app.component.css
.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);}
Puedes usar el token de inyección CDK_DROP_LIST que se puede usar para referenciar instancias de cdkDropList. Para más información consulta la guía de inyección de dependencias y la API del token de inyección de lista (drop list).
Transferir elementos arrastrables entre listas (drop lists)
La directiva cdkDropList soporta transferir elementos arrastrables entre listas conectadas (connected drop lists). Hay dos formas de conectar una o más instancias de cdkDropList:
- Establecer la propiedad
cdkDropListConnectedToa otra lista (drop list). - Envolver los elementos en un elemento con el atributo
cdkDropListGroup.
La directiva cdkDropListConnectedTo funciona tanto con una referencia directa a otro cdkDropList como referenciando el id de otro contenedor (drop container).
<!-- This is valid --><div cdkDropList #listOne="cdkDropList" [cdkDropListConnectedTo]="[listTwo]"></div><div cdkDropList #listTwo="cdkDropList" [cdkDropListConnectedTo]="[listOne]"></div><!-- This is valid as well --><div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div><div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
app/app.component.html
<div class="example-container"> <h2>To do</h2> <div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList]" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of todo; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div></div><div class="example-container"> <h2>Done</h2> <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[todoList]" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of done; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div></div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray, transferArrayItem,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop connected sorting */@Component({ selector: 'cdk-drag-drop-connected-sorting-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropConnectedSortingExample { todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep']; done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog']; drop(event: CdkDragDrop<string[]>) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex, ); } }}
app/app.component.css
.example-container { width: 400px; max-width: 100%; margin: 0 25px 25px 0; display: inline-block; vertical-align: top;}.example-list { border: solid 1px #ccc; min-height: 60px; background: white; border-radius: 4px; overflow: hidden; display: block;}.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 { 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);}
Usa la directiva cdkDropListGroup si tienes un número desconocido de listas conectadas (connected drop lists) para establecer la conexión automáticamente. Cualquier nuevo cdkDropList que se agregue bajo un grupo se conecta automáticamente a todas las otras listas.
<div cdkDropListGroup> <!-- Todas las listas aquí estarán conectadas. --> @for (list of lists; track list) { <div cdkDropList></div> }</div>
app/app.component.html
<div cdkDropListGroup> <div class="example-container"> <h2>To do</h2> <div cdkDropList [cdkDropListData]="todo" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of todo; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div> </div> <div class="example-container"> <h2>Done</h2> <div cdkDropList [cdkDropListData]="done" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of done; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div> </div></div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDropList, CdkDropListGroup, moveItemInArray, transferArrayItem,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop connected sorting group */@Component({ selector: 'cdk-drag-drop-connected-sorting-group-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropListGroup, CdkDropList, CdkDrag],})export class CdkDragDropConnectedSortingGroupExample { todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep']; done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog']; drop(event: CdkDragDrop<string[]>) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex, ); } }}
app/app.component.css
.example-container { width: 400px; max-width: 100%; margin: 0 25px 25px 0; display: inline-block; vertical-align: top;}.example-list { border: solid 1px #ccc; min-height: 60px; background: white; border-radius: 4px; overflow: hidden; display: block;}.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 { 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);}
Puedes usar el token de inyección CDK_DROP_LIST_GROUP que se puede usar para referenciar instancias de cdkDropListGroup. Para más información consulta la guía de inyección de dependencias y la API del token de inyección del grupo de lista (drop list).
Arrastre selectivo
Por defecto, un usuario puede mover elementos cdkDrag de un contenedor a otro contenedor conectado. Para un control más granular sobre qué elementos se pueden soltar en un contenedor, usa cdkDropListEnterPredicate. Angular llama al predicado cada vez que un elemento arrastrable entra en un nuevo contenedor. Dependiendo de si el predicado devuelve true o false, el elemento puede o no ser permitido en el nuevo contenedor.
app/app.component.html
<div class="example-container"> <h2>Available numbers</h2> <div id="all" cdkDropList [cdkDropListData]="all" cdkDropListConnectedTo="even" class="example-list" (cdkDropListDropped)="drop($event)" [cdkDropListEnterPredicate]="noReturnPredicate"> @for (number of all; track number) { <div class="example-box" [cdkDragData]="number" cdkDrag>{{number}}</div> } </div></div><div class="example-container"> <h2>Even numbers</h2> <div id="even" cdkDropList [cdkDropListData]="even" cdkDropListConnectedTo="all" class="example-list" (cdkDropListDropped)="drop($event)" [cdkDropListEnterPredicate]="evenPredicate"> @for (number of even; track number) { <div class="example-box" cdkDrag [cdkDragData]="number">{{number}}</div> } </div></div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray, transferArrayItem,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop enter predicate */@Component({ selector: 'cdk-drag-drop-enter-predicate-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropEnterPredicateExample { all = [1, 2, 3, 4, 5, 6, 7, 8, 9]; even = [10]; drop(event: CdkDragDrop<number[]>) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex, ); } } /** Predicate function that only allows even numbers to be dropped into a list. */ evenPredicate(item: CdkDrag<number>) { return item.data % 2 === 0; } /** Predicate function that doesn't allow items to be dropped into a list. */ noReturnPredicate() { return false; }}
app/app.component.css
.example-container { width: 400px; max-width: 100%; margin: 0 25px 25px 0; display: inline-block; vertical-align: top;}.example-list { border: solid 1px #ccc; min-height: 60px; background: white; border-radius: 4px; overflow: hidden; display: block;}.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 { 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);}
Adjuntar datos
Puedes asociar algunos datos arbitrarios tanto con cdkDrag como con cdkDropList estableciendo cdkDragData o cdkDropListData, respectivamente. Puedes vincular a los eventos disparados desde ambas directivas que incluirán estos datos, permitiéndote identificar fácilmente el origen de la interacción de arrastre o soltar.
@for (list of lists; track list) { <div cdkDropList [cdkDropListData]="list" (cdkDropListDropped)="drop($event)"> @for (item of list; track item) { <div cdkDrag [cdkDragData]="item"></div> } </div>}
Personalizaciones de arrastre
Personalizar el manejador de arrastre
Por defecto, el usuario puede arrastrar todo el elemento cdkDrag para moverlo. Para restringir al usuario a que solo pueda hacerlo usando un elemento manejador, añade la directiva cdkDragHandle a un elemento dentro de cdkDrag. Puedes tener tantos elementos cdkDragHandle como desees.
app/app.component.html
<div class="example-box" cdkDrag> I can only be dragged using the handle <div class="example-handle" cdkDragHandle> <svg width="24px" fill="currentColor" viewBox="0 0 24 24"> <path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"></path> <path d="M0 0h24v24H0z" fill="none"></path> </svg> </div></div>
app/app.component.ts
import {CdkDrag, CdkDragHandle} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop with a handle */@Component({ selector: 'cdk-drag-drop-handle-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag, CdkDragHandle],})export class CdkDragDropHandleExample {}
app/app.component.css
.example-box { width: 200px; height: 200px; padding: 10px; box-sizing: border-box; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); display: flex; justify-content: center; align-items: center; text-align: center; background: #fff; border-radius: 4px; position: relative; z-index: 1; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-box:active { 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);}.example-handle { position: absolute; top: 10px; right: 10px; color: #ccc; cursor: move; width: 24px; height: 24px;}
Puedes usar el token de inyección CDK_DRAG_HANDLE que se puede usar para referenciar instancias de cdkDragHandle. Para más información consulta la guía de inyección de dependencias y la API del token de inyección de manejador de arrastre.
Personalizar vista previa de arrastre
Un elemento de vista previa se vuelve visible cuando un elemento cdkDrag está siendo arrastrado. Por defecto, la vista previa es un clon del elemento original posicionado junto al cursor del usuario.
Para personalizar la vista previa, proporciona una plantilla personalizada a través de *cdkDragPreview. La vista previa personalizada no coincidirá con el tamaño del elemento original arrastrado ya que no se hacen suposiciones sobre el contenido del elemento. Para que coincida con el tamaño del elemento para la vista previa de arrastre, pasa true al input matchSize.
El elemento clonado elimina su atributo id para evitar tener múltiples elementos con el mismo id en la página. Esto hará que cualquier CSS que apunte a ese id no se aplique.
app/app.component.html
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)"> @for (movie of movies; track movie) { <div class="example-box" cdkDrag> {{movie.title}} <img *cdkDragPreview [src]="movie.poster" [alt]="movie.title"> </div> }</div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDragPreview, CdkDropList, moveItemInArray,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop custom preview */@Component({ selector: 'cdk-drag-drop-custom-preview-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag, CdkDragPreview],})export class CdkDragDropCustomPreviewExample { // tslint:disable:max-line-length movies = [ { title: 'Episode I - The Phantom Menace', poster: 'https://upload.wikimedia.org/wikipedia/en/4/40/Star_Wars_Phantom_Menace_poster.jpg', }, { title: 'Episode II - Attack of the Clones', poster: 'https://upload.wikimedia.org/wikipedia/en/3/32/Star_Wars_-_Episode_II_Attack_of_the_Clones_%28movie_poster%29.jpg', }, { title: 'Episode III - Revenge of the Sith', poster: 'https://upload.wikimedia.org/wikipedia/en/9/93/Star_Wars_Episode_III_Revenge_of_the_Sith_poster.jpg', }, { title: 'Episode IV - A New Hope', poster: 'https://upload.wikimedia.org/wikipedia/en/8/87/StarWarsMoviePoster1977.jpg', }, { title: 'Episode V - The Empire Strikes Back', poster: 'https://upload.wikimedia.org/wikipedia/en/3/3f/The_Empire_Strikes_Back_%281980_film%29.jpg', }, { title: 'Episode VI - Return of the Jedi', poster: 'https://upload.wikimedia.org/wikipedia/en/b/b2/ReturnOfTheJediPoster1983.jpg', }, { title: 'Episode VII - The Force Awakens', poster: 'https://upload.wikimedia.org/wikipedia/en/a/a2/Star_Wars_The_Force_Awakens_Theatrical_Poster.jpg', }, { title: 'Episode VIII - The Last Jedi', poster: 'https://upload.wikimedia.org/wikipedia/en/7/7f/Star_Wars_The_Last_Jedi.jpg', }, { title: 'Episode IX – The Rise of Skywalker', poster: 'https://upload.wikimedia.org/wikipedia/en/a/af/Star_Wars_The_Rise_of_Skywalker_poster.jpg', }, ]; // tslint:enable:max-line-length drop(event: CdkDragDrop<{title: string; poster: string}[]>) { moveItemInArray(this.movies, event.previousIndex, event.currentIndex); }}
app/app.component.css
.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 { 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);}
Puedes usar el token de inyección CDK_DRAG_PREVIEW que se puede usar para referenciar instancias de cdkDragPreview. Para más información consulta la guía de inyección de dependencias y la API del token de inyección de vista previa de arrastre.
Personalizar punto de inserción de arrastre
Por defecto, Angular inserta la vista previa de cdkDrag en el <body> de la página para evitar problemas con el posicionamiento y el desbordamiento. Esto puede no ser deseable en algunos casos porque la vista previa no tendrá sus estilos heredados aplicados.
Puedes cambiar dónde Angular inserta la vista previa usando el input cdkDragPreviewContainer en cdkDrag. Los valores posibles son:
| Valor | Descripción | Ventajas | Desventajas |
|---|---|---|---|
global |
Valor por defecto. Angular inserta la vista previa en el o en el shadow root (shadow root (raíz de sombra)) más cercano. | La vista previa no se verá afectada por z-index o overflow: hidden. Tampoco afectará selectores :nth-child y layouts flex. |
No retiene estilos heredados. |
parent |
Angular inserta la vista previa dentro del padre del elemento que está siendo arrastrado. | La vista previa hereda los mismos estilos que el elemento arrastrado. | La vista previa puede ser recortada por overflow: hidden o colocada debajo de otros elementos debido a z-index. Además, puede afectar selectores :nth-child y algunos layouts flex. |
ElementRef or HTMLElement |
Angular inserta la vista previa en el elemento especificado. | PLa vista previa hereda estilos del elemento contenedor especificado. | La vista previa puede ser recortada por overflow: hidden o colocada debajo de otros elementos debido a z-index. Además, puede afectar selectores :nth-child y algunos layouts flex. |
Alternativamente, puedes modificar el token de inyección CDK_DRAG_CONFIG para actualizar previewContainer dentro de la configuración si el valor es global o parent. Para más información consulta la guía de inyección de dependencias, la API del token de inyección de configuración de arrastre, y la API de configuración de drag and drop.
Personalizar marcador de posición de arrastre
Mientras un elemento cdkDrag está siendo arrastrado, la directiva crea un elemento marcador de posición que muestra dónde se colocará el elemento cuando se suelte. Por defecto, el marcador de posición es un clon del elemento que está siendo arrastrado. Puedes reemplazar el marcador de posición con uno personalizado usando la directiva *cdkDragPlaceholder:
app/app.component.html
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)"> @for (movie of movies; track movie) { <div class="example-box" cdkDrag> <div class="example-custom-placeholder" *cdkDragPlaceholder></div> {{movie}} </div> }</div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDragPlaceholder, CdkDropList, moveItemInArray,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop custom placeholder */@Component({ selector: 'cdk-drag-drop-custom-placeholder-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag, CdkDragPlaceholder],})export class CdkDragDropCustomPlaceholderExample { 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); }}
app/app.component.css
.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 { 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-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);}.example-custom-placeholder { background: #ccc; border: dotted 3px #999; min-height: 60px; transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);}
Puedes usar el token de inyección CDK_DRAG_PLACEHOLDER que se puede usar para referenciar instancias de cdkDragPlaceholder. Para más información consulta la guía de inyección de dependencias y la API del token de inyección de marcador de posición de arrastre.
Personalizar elemento raíz de arrastre
Establece el atributo cdkDragRootElement si hay un elemento que quieres hacer arrastrable pero no tienes acceso directo a él.
El atributo acepta un selector y busca en el DOM hasta encontrar un elemento que coincida con el selector. Si se encuentra un elemento, se vuelve arrastrable. Esto es útil para casos como hacer un diálogo arrastrable.
app/app.component.html
<button type="button" (click)="openDialog()">Open a draggable dialog</button><ng-template> <div class="example-dialog-content" cdkDrag cdkDragRootElement=".cdk-overlay-pane"> Drag the dialog around! </div></ng-template>
app/app.component.ts
import {CdkDrag} from '@angular/cdk/drag-drop';import {Overlay, OverlayRef} from '@angular/cdk/overlay';import {TemplatePortal} from '@angular/cdk/portal';import { AfterViewInit, Component, OnDestroy, TemplateRef, ViewChild, ViewContainerRef, inject,} from '@angular/core';/** * @title Drag&Drop with alternate root element */@Component({ selector: 'cdk-drag-drop-root-element-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag],})export class CdkDragDropRootElementExample implements AfterViewInit, OnDestroy { private _overlay = inject(Overlay); private _viewContainerRef = inject(ViewContainerRef); @ViewChild(TemplateRef) _dialogTemplate!: TemplateRef<any>; private _overlayRef!: OverlayRef; private _portal!: TemplatePortal; ngAfterViewInit() { this._portal = new TemplatePortal(this._dialogTemplate, this._viewContainerRef); this._overlayRef = this._overlay.create({ positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(), hasBackdrop: true, }); this._overlayRef.backdropClick().subscribe(() => this._overlayRef.detach()); } ngOnDestroy() { this._overlayRef.dispose(); } openDialog() { this._overlayRef.attach(this._portal); }}
app/app.component.css
.example-dialog-content { width: 200px; height: 200px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); cursor: move; display: flex; justify-content: center; align-items: center; background: #fff; border-radius: 4px; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-dialog-content:active { 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);}
Alternativamente, puedes modificar el token de inyección CDK_DRAG_CONFIG para actualizar rootElementSelector dentro de la configuración. Para más información consulta la guía de inyección de dependencias, la API del token de inyección de configuración de arrastre, y la API de configuración de drag and drop.
Establecer la posición DOM de un elemento arrastrable
Por defecto, los elementos cdkDrag que no están en un cdkDropList se mueven de su posición DOM normal solo cuando un usuario mueve manualmente el elemento. Usa el input cdkDragFreeDragPosition para establecer explícitamente la posición del elemento. Un caso de uso común para esto es restaurar la posición de un elemento arrastrable después de que un usuario ha navegado y luego regresado.
app/app.component.html
<p> <button type="button" (click)="changePosition()">Change element position</button></p><div class="example-box" cdkDrag [cdkDragFreeDragPosition]="dragPosition"> Drag me around</div>
app/app.component.ts
import {CdkDrag} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Programmatically setting the free drag position */@Component({ selector: 'cdk-drag-drop-free-drag-position-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag],})export class CdkDragDropFreeDragPositionExample { dragPosition = {x: 0, y: 0}; changePosition() { this.dragPosition = {x: this.dragPosition.x + 50, y: this.dragPosition.y + 50}; }}
app/app.component.css
.example-box { width: 200px; height: 200px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); cursor: move; display: flex; justify-content: center; align-items: center; text-align: center; background: #fff; border-radius: 4px; position: relative; z-index: 1; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-box:active { 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);}
Restrict movement within an element
To stop the user from being able to drag a cdkDrag element outside of another element, pass a CSS selector to the cdkDragBoundary attribute. This attribute accepts a selector and looks up the DOM until it finds an element that matches it. If a match is found, the element becomes the boundary that the draggable element can't be dragged outside of cdkDragBoundary can also be used when cdkDrag is placed inside a cdkDropList.
app/app.component.html
<div class="example-boundary"> <div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag> I can only be dragged within the dotted container </div></div>
app/app.component.ts
import {CdkDrag} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop boundary */@Component({ selector: 'cdk-drag-drop-boundary-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag],})export class CdkDragDropBoundaryExample {}
app/app.component.css
.example-box { width: 200px; height: 200px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); cursor: move; display: inline-flex; justify-content: center; align-items: center; text-align: center; background: #fff; border-radius: 4px; margin-right: 25px; position: relative; z-index: 1; box-sizing: border-box; padding: 10px; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-box:active { 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);}.example-boundary { width: 400px; height: 400px; max-width: 100%; border: dotted #ccc 2px;}
Alternatively, you can modify the CDK_DRAG_CONFIG injection token to update boundaryElement within the config. For more information see the dependency injection guide, drag config injection token API, and the drag drop config API.
Restringir movimiento a lo largo de un eje
Por defecto, cdkDrag permite movimiento libre en todas las direcciones. Para restringir el arrastre a un eje específico, establece cdkDragLockAxis a "x" o "y" en cdkDrag. Para restringir el arrastre para múltiples elementos arrastrables dentro de cdkDropList, establece cdkDropListLockAxis en cdkDropList en su lugar.
app/app.component.html
<div class="example-box" cdkDragLockAxis="y" cdkDrag> I can only be dragged up/down</div><div class="example-box" cdkDragLockAxis="x" cdkDrag> I can only be dragged left/right</div>
app/app.component.ts
import {CdkDrag} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop position locking */@Component({ selector: 'cdk-drag-drop-axis-lock-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag],})export class CdkDragDropAxisLockExample {}
app/app.component.css
.example-box { width: 200px; height: 200px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); cursor: move; display: inline-flex; justify-content: center; align-items: center; text-align: center; background: #fff; border-radius: 4px; margin-right: 25px; position: relative; z-index: 1; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-box:active { 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);}
Alternativamente, puedes modificar el token de inyección CDK_DRAG_CONFIG para actualizar lockAxis dentro de la configuración. Para más información consulta la guía de inyección de dependencias, la API del token de inyección de configuración de arrastre, y la API de configuración de drag and drop.
Retrasar el arrastre
Por defecto, cuando el usuario presiona su puntero en un cdkDrag, comienza la secuencia de arrastre. Este comportamiento puede no ser deseable en casos como elementos arrastrables de pantalla completa en dispositivos táctiles donde el usuario podría activar accidentalmente un evento de arrastre mientras hace scroll en la página.
Puedes retrasar la secuencia de arrastre usando el input cdkDragStartDelay. El input espera a que el usuario mantenga presionado su puntero durante el número especificado de milisegundos antes de arrastrar el elemento.
app/app.component.html
<div class="example-box" cdkDrag [cdkDragStartDelay]="1000"> Dragging starts after one second</div>
app/app.component.ts
import {CdkDrag} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Delay dragging */@Component({ selector: 'cdk-drag-drop-delay-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDrag],})export class CdkDragDropDelayExample {}
app/app.component.css
.example-box { width: 200px; height: 200px; border: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); cursor: move; display: flex; justify-content: center; align-items: center; text-align: center; background: #fff; border-radius: 4px; position: relative; z-index: 1; transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1); box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);}.example-box:active { 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);}
Alternatively, you can modify the CDK_DRAG_CONFIG injection token to update dragStartDelay within the config. For more information see the dependency injection guide, drag config injection token API, and the drag drop config API.
Deshabilitar el arrastre
Si quieres deshabilitar el arrastre para un elemento de arrastre particular, establece el input cdkDragDisabled en un elemento cdkDrag a true o false. Puedes deshabilitar una lista completa usando el input cdkDropListDisabled en un cdkDropList. También es posible deshabilitar una manija específica a través de cdkDragHandleDisabled en cdkDragHandle.
app/app.component.html
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of items; track item) { <div class="example-box" cdkDrag [cdkDragDisabled]="item.disabled">{{item.value}}</div> }</div>
app/app.component.ts
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop disabled */@Component({ selector: 'cdk-drag-drop-disabled-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropDisabledExample { items = [ {value: 'I can be dragged', disabled: false}, {value: 'I cannot be dragged', disabled: true}, {value: 'I can also be dragged', disabled: false}, ]; drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.items, event.previousIndex, event.currentIndex); }}
app/app.component.css
.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;}.example-box.cdk-drag-disabled { background: #ccc; cursor: not-allowed; user-select: none;}.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);}
Alternativamente, puedes modificar el token de inyección CDK_DRAG_CONFIG para actualizar draggingDisabled dentro de la configuración. Para más información consulta la guía de inyección de dependencias, la API del token de inyección de configuración de arrastre, y la API de configuración de drag and drop.
Personalizaciones de ordenamiento
Orientación de lista
Por defecto, la directiva cdkDropList asume que las listas son verticales. Esto se puede cambiar estableciendo la propiedad cdkDropListOrientation a horizontal.
app/app.component.html
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)"> @for (timePeriod of timePeriods; track timePeriod) { <div class="example-box" cdkDrag>{{timePeriod}}</div> }</div>
app/app.component.ts
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop horizontal sorting */@Component({ selector: 'cdk-drag-drop-horizontal-sorting-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropHorizontalSortingExample { timePeriods = [ 'Bronze age', 'Iron age', 'Middle ages', 'Early modern period', 'Long nineteenth century', ]; drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex); }}
app/app.component.css
.example-list { width: 1000px; 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);}
Alternativamente, puedes modificar el token de inyección CDK_DRAG_CONFIG para actualizar listOrientation dentro de la configuración. Para más información consulta la guía de inyección de dependencias, la API del token de inyección de configuración de arrastre, y la API de configuración de drag and drop.
Envoltura de la lista
Por defecto, el cdkDropList ordena los elementos arrastrables moviéndolos usando una transformación CSS. Esto permite que el ordenamiento sea animado, lo que proporciona una mejor experiencia de usuario. Sin embargo, esto también viene con la desventaja de que la lista (drop list) funciona solo en una dirección: vertical u horizontalmente.
Si tienes una lista ordenable que necesita envolverse en nuevas líneas, puedes establecer el atributo cdkDropListOrientation a mixed. Esto hace que la lista use una estrategia diferente de ordenamiento de elementos que implica moverlos en el DOM. Sin embargo, la lista ya no puede animar la acción de ordenamiento.
app/app.component.html
<div cdkDropList cdkDropListOrientation="mixed" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of items; track item) { <div class="example-box" cdkDrag>{{item}}</div> }</div>
app/app.component.ts
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop horizontal wrapping list */@Component({ selector: 'cdk-drag-drop-mixed-sorting-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropMixedSortingExample { items = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']; drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.items, event.previousIndex, event.currentIndex); }}
app/app.component.css
.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);}
Ordenamiento selectivo
Por defecto, los elementos cdkDrag se ordenan en cualquier posición dentro de un cdkDropList. Para cambiar este comportamiento, establece el atributo cdkDropListSortPredicate que toma una función. La función predicado se llama cada vez que un elemento arrastrable está a punto de ser movido a un nuevo índice dentro de la lista (drop list). Si el predicado devuelve true, el elemento se moverá al nuevo índice, de lo contrario mantendrá su posición actual.
app/app.component.html
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)" [cdkDropListSortPredicate]="sortPredicate"> @for (number of numbers; track number) { <div class="example-box" [cdkDragData]="number" cdkDrag>{{number}}</div> }</div>
app/app.component.ts
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop sort predicate */@Component({ selector: 'cdk-drag-drop-sort-predicate-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropList, CdkDrag],})export class CdkDragDropSortPredicateExample { numbers = [1, 2, 3, 4, 5, 6, 7, 8]; drop(event: CdkDragDrop<unknown>) { moveItemInArray(this.numbers, event.previousIndex, event.currentIndex); } /** * Predicate function that only allows even numbers to be * sorted into even indices and odd numbers at odd indices. */ sortPredicate(index: number, item: CdkDrag<number>) { return (index + 1) % 2 === item.data % 2; }}
app/app.component.css
.example-list { border: solid 1px #ccc; min-height: 60px; background: white; border-radius: 4px; overflow: hidden; display: block; width: 400px; max-width: 100%;}.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 { 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);}
Deshabilitar ordenamiento
Hay casos donde los elementos arrastrables pueden ser arrastrados de un cdkDropList a otro, sin embargo, el usuario no debería poder ordenarlos dentro de la lista de origen. Para estos casos, agrega el atributo cdkDropListSortingDisabled para evitar que los elementos arrastrables en un cdkDropList se ordenen. Esto preserva la posición inicial del elemento arrastrado en la lista de origen si no se arrastra a una nueva posición válida.
app/app.component.html
<div cdkDropListGroup> <div class="example-container"> <h2>Available items</h2> <div cdkDropList [cdkDropListData]="items" class="example-list" cdkDropListSortingDisabled (cdkDropListDropped)="drop($event)"> @for (item of items; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div> </div> <div class="example-container"> <h2>Shopping basket</h2> <div cdkDropList [cdkDropListData]="basket" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of basket; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div> </div></div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDropList, CdkDropListGroup, moveItemInArray, transferArrayItem,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop disabled sorting */@Component({ selector: 'cdk-drag-drop-disabled-sorting-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', standalone: true, imports: [CdkDropListGroup, CdkDropList, CdkDrag],})export class CdkDragDropDisabledSortingExample { items = ['Carrots', 'Tomatoes', 'Onions', 'Apples', 'Avocados']; basket = ['Oranges', 'Bananas', 'Cucumbers']; drop(event: CdkDragDrop<string[]>) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex, ); } }}
app/app.component.css
HTMLTSCSS.example-container { width: 400px; max-width: 100%; margin: 0 25px 25px 0; display: inline-block; vertical-align: top;}.example-list { border: solid 1px #ccc; min-height: 60px; background: white; border-radius: 4px; overflow: hidden; display: block;}.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 { 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);}
Alternativamente, puedes modificar el token de inyección CDK_DRAG_CONFIG para actualizar sortingDisabled dentro de la configuración. Para más información consulta la guía de inyección de dependencias, la API del token de inyección de configuración de arrastre, y la API de configuración de drag and drop.
Copying items between lists
By default, when an item is dragged from one list to another, it is moved out of its original list. However, you can configure the directives to copy the item, leaving the original item in the source list.
To enable copying, you can set the cdkDropListHasAnchor input. This tells the cdkDropList to create an "anchor" element that stays in the original container and doesn't move with the item. If the user moves the item back into the original container, the anchor is removed automatically. The anchor element can be styled by targeting the .cdk-drag-anchor CSS class.
Combining cdkDropListHasAnchor with cdkDropListSortingDisabled makes it possible to construct a list from which a user can copy items without being able to reorder the source list (e.g. a product list and a shopping cart).
app/app.component.html
<div class="example-container"> <h2>Products</h2> <div cdkDropList [cdkDropListData]="products" [cdkDropListConnectedTo]="[cartList]" cdkDropListSortingDisabled cdkDropListHasAnchor class="example-list" > @for (product of products; track $index) { <div class="example-box" cdkDrag [cdkDragData]="product">{{product}}</div> } </div></div><div class="example-container"> <h2>Shopping cart</h2> <div cdkDropList #cartList="cdkDropList" [cdkDropListData]="cart" class="example-list" (cdkDropListDropped)="drop($event)" > @for (product of cart; track $index) { <div class="example-box" cdkDrag>{{product}}</div> } </div></div>
app/app.component.ts
import { CdkDrag, CdkDragDrop, CdkDropList, copyArrayItem, moveItemInArray,} from '@angular/cdk/drag-drop';import {Component} from '@angular/core';/** * @title Drag&Drop copy between lists */@Component({ selector: 'cdk-drag-drop-copy-list-example', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [CdkDropList, CdkDrag],})export class CdkDragDropCopyListExample { products = ['Bananas', 'Oranges', 'Bread', 'Butter', 'Soda', 'Eggs']; cart = ['Tomatoes']; drop(event: CdkDragDrop<string[]>) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { copyArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex, ); } }}
app/app.component.css
.example-container { width: 400px; max-width: 100%; margin: 0 25px 25px 0; display: inline-block; vertical-align: top; font-family: sans-serif;}.example-list { border: solid 1px #ccc; min-height: 60px; background: white; border-radius: 4px; overflow: hidden; display: block;}.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; font-family: sans-serif;}.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-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);}
Personalizar animaciones
Drag and drop soporta animaciones tanto para:
- Ordenar un elemento arrastrable dentro de una lista
- Mover el elemento arrastrable desde la posición donde el usuario lo soltó a la posición final dentro de la lista
Para configurar tus animaciones, define una transición CSS que apunte a la propiedad transform. Las siguientes clases se pueden usar para animaciones:
| Nombre de la clase CSS | Resultado de añadir la transición |
|---|---|
| .cdk-drag | Anima los elementos arrastrables a medida que se ordenan. |
| .cdk-drag-animating | Animar el elemento arrastrable desde su posición soltada a la posición final dentro del cdkDropList.Esta clase CSS se aplica a un elemento cdkDrag solo cuando la acción de arrastre se ha detenido. |
Styling
Tanto las directivas cdkDrag como cdkDropList solo aplican estilos esenciales necesarios para la funcionalidad. Las aplicaciones pueden personalizar sus estilos apuntando a estas clases CSS especificadas.
| Nombre de la clase CSS | Descripción |
|---|---|
| .cdk-drop-list | Selector para los elementos contenedores de cdkDropList. |
| .cdk-drag | Selector para elementos cdkDrag. |
| .cdk-drag-disabled | Selector para elementos cdkDrag deshabilitados. |
| .cdk-drag-handle | Selector para el elemento host del cdkDragHandle. |
| .cdk-drag-preview | Selector para el elemento de vista previa de arrastre. Este es el elemento que aparece junto al cursor cuando un usuario arrastra un elemento en una lista ordenable. El elemento se ve exactamente como el elemento que está siendo arrastrado a menos que se personalice con una plantilla personalizada a través de *cdkDragPreview. |
| .cdk-drag-placeholder | Selector para el elemento marcador de posición de arrastre. Este es el elemento que se muestra en el lugar donde el elemento arrastrable será arrastrado una vez que termine la acción de arrastre. Este elemento se ve exactamente como el elemento que está siendo ordenado a menos que se personalice con la directiva cdkDragPlaceholder. |
| .cdk-drop-list-dragging | Selector para el elemento contenedor de cdkDropList que tiene un elemento arrastrable actualmente siendo arrastrado. |
| .cdk-drop-list-disabled | Selector para elementos contenedores de cdkDropList que están deshabilitados. |
| .cdk-drop-list-receiving | Selector para el elemento contenedor de cdkDropList que tiene un elemento arrastrable que puede recibir de una lista de soltar conectada que está actualmente siendo arrastrado. |
| .cdk-drag-anchor | Selector para elemento de aclaje que se crea cuado cdkDropListHasAnchor está habilitado. Este elemento indica la posición desde la cual comenzó el arrastre del elemento |
Arrastrado en un contenedor desplazable
Si tus elementos arrastrables están dentro de un contenedor desplazable (por ejemplo, un div con overflow: auto), el desplazamiento automático no funcionará a menos que el contenedor desplazable tenga la directiva cdkScrollable. Sin esta directiva, el CDK no puede detectar ni controlar el comportamiento de desplazamiento del contenedor durante las operaciones de arrastre.
Integraciones con otros componentes
La funcionalidad de arrastrar y soltar del CDK puede integrarse con diferentes componentes. Los casos de uso más comunes incluyen componentes MatTable ordenables y componentes MatTabGroup ordenables.