Angular Material is a User Interface (UI) component library that developers can use to quickly create stunning and cohesive user interfaces in their Angular projects. With Angular Material, you can find reusable and appealing UI components like Cards, Inputs, Data Tables, Datepickers, and much more. Each component is ready to be used in the default style according to the Material Design specification. Still, you can easily customize the look of Angular Material components. The available list of Angular Material components grows with each library iteration.
Drag and drop is a platform where applications use drag-and-drop functionality on browsers. The user clicks and drags files to a droppable element (drop zone) using a mouse or touchpad, then releases the mouse button to release the files. Angular Drag and Drop CDK (Component Dev Kit) provides support for free dragging, sorting within a list, moving items between lists, animations, touch devices, custom drag handles, and more. The @angular/cdk/drag-drop
module also allows you to create drag-and-drop interfaces quickly and declaratively.
The first step is to install Angular Material UI into our Project.
ng add @angular/material
The DragDropModule
will be imported into NgModule
:
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {DragDropModule} from '@angular/cdk/drag-drop';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
DragDropModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now that we’ve imported the module, we can create our first draggable component by using the cdkDrag
directive.
<div class="box" cdkDrag>
Drag me around
</div>
We can already drag and drop in our project after running the code.
The next step is to build a drop zone now that we know how to drag an element. We’ll utilize the new directive cdkDropList
to do this; it will serve as a container to dump the draggable elements into. The item will return to its original location inside the drop zone if we attempt to dump it outside the drop zone.
<div cdkDropList>
<div class="box" cdkDrag>
Drag me around
</div>
</div>
The next stage is dragging and re-ordering things inside a list after learning to make a draggable item and a drop zone. To generate the list components inside a cdkDrop
container, we'll utilize the *ngFor
directive.
<div class="box" cdkDropList>
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
</div>
The elements are defined as a string array in the AppComponent
:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
items = ['Football', 'Tennis', 'Basketball', 'Rugby', 'Golf']
}
The GIF below shows how things automatically rearrange themselves as we drag them. When we drag and drop anything, it returns to its original place.
We must implement the cdkDropDropped
method in order to resolve this issue and save the new index when an item is dropped inside the list. When a user drops something inside the drop zone, the dropped function is always called. Its trademark is as follows:
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';
@Component({...})
export class AppComponent {
title = 'dropzone';
items = [...]
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
}
The utility function moveItemInArray
is also included in the drag and drop CDK, as shown in the code above. The array's dropped item's new index is determined using this function, rad! It's time to link the dropped
function to the HTML's cdkDrop
element now that we have implemented it.
<div class="box" cdkDropList
(cdkDropListDropped)="drop($event)">
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
</div>
This results in elements inside the cdkDrop
container being draggable and reorderable. Click here for a better understanding.
Let’s go one step farther and construct a straightforward task board. To do this, we will divide the items
array into three smaller arrays: one for new items, one for active items, and one for completed items.
incomingGoods = ['Tomatoes', 'Carrots', 'Onions', 'Pepper']
availableGoods = ['Cucumber']
soldGoods = ['Orange', 'Apple', 'Banana']
Three different lists must be displayed, and each list will have its own drop zone. By using the cdkDropData
input, we can connect the arrays to a drop zone.
<div
cdkDrop
#new="cdkDrop"
[cdkDropData]="newItems"
[cdkDropConnectedTo]="[active]"
(cdkDropDropped)="dropped($event)"
>
<div *ngFor="let item of newItems" cdkDrag>{{ item }}</div>
</div>
A cdkDrop
list instance can be linked to another cdkDrop
list instance using the [cdkDropConnectedTo]
input attribute. We won't be able to drag and drop the things to another list if we don't do this. The connections that need to be made in our task board example are as follows:
incomingGoods
to the availableGoods
list;availableGoods
to the incomingGoods
and soldGoods
list;soldGoods
list onto the availableGoods
;To put it another way, you can drag a incomingGoods
to the availableGoods
, the soldGoods
, or the reverse order. However, you must first move through the availableGoods
to drag a incomingGoods
to the soldGoods
. Combining these results yields the following:
<div cdkDropListGroup>
<div class="container">
<h2>Incoming Goods</h2>
<div
id="incoming"
cdkDropList
[cdkDropListData]="incomingItems"
cdkDropListConnectedTo="available"
class="list"
(cdkDropListDropped)="drop($event)"
[cdkDropListEnterPredicate]="noReturnPredicate">
<div class="box" *ngFor="let item of incomingItems" cdkDrag>{{item}}</div>
</div>
</div>
<div class="container">
<h2>Available Goods</h2>
<div
id="available"
cdkDropList
[cdkDropListData]="availableItems"
cdkDropListConnectedTo="sold"
class="list"
(cdkDropListDropped)="drop($event)"
>
<div class="box" *ngFor="let item of availableItems" cdkDrag>{{item}}</div>
</div>
</div>
<div class="container">
<h2>Sold Out Goods</h2>
<div
id="sold"
cdkDropList
[cdkDropListData]="soldItems"
cdkDropListConnectedTo="available"
class="list"
(cdkDropListDropped)="drop($event)"
>
<div class="box" *ngFor="let item of soldItems" cdkDrag>{{item}}</div>
</div>
</div>
</div>
Making our dropped
function smarter is the final phase; to meet our needs, it must transfer items from one list to another list.
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
dropped(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
);
}
}
It rearranges the elements in the same previous order if the container is the same. The dragged item gets transferred to the list where it is being dumped if the container differs. Once more, a useful function called transferArrayItem
is included right out of the box. Check can the documentation.
Setting the cdkDragDisabled
input on a cdkDrag
item will disable dragging for that particular drag item. Additionally, you can disable a specific handle using the cdkDragHandleDisabled
input on a cdkDragHandle
or an entire list using the cdkDropListDisabled
input on a cdkDropList
.
<div cdkDropList class="list" (cdkDropListDropped)="drop($event)">
<div
class="box"
*ngFor="let item of items"
cdkDrag
[cdkDragDisabled]="item.disabled">{{item.value}}</div>
</div>
In our app.component.ts
we set the boolean function of our value to either true/false.
import {Component} from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
items = [
{value: 'Oranges', disabled: false},
{value: 'Bananas', disabled: true},
{value: 'Mangoes', disabled: false},
];
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
Our value ‘Banana’ is set disabled:true
, which invokes the cdkDragDisabled
to disable dragging for that particular item. Click here for more information.
Angular Material is flat and extremely simple by design. It is made with the understanding that adding new CSS rules is considerably simpler than changing already-existing CSS rules. This is simple and intuitive to use, but it also offers flexibility by allowing you to take charge when needed.
You can get more information by consulting the sourcecode and documentation.
Important resources: