Back

Drag-and-drop with Angular Material

Drag-and-drop with Angular Material

Angular Material is a UI component library that developers can use to quickly create stunning and cohesive user interfaces in their projects. Within Angular Material, you can find reusable 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 these components. The available list of Angular Material components grows with each library iteration.

With drag and drop users can click and drag files to a droppable element (drop zone) using a mouse or touchpad, then by releasing the mouse button they “drop” the files. This allows you to build very intuitive user experiences relatively easy. 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.

And that is what we’re going to be doind in this article, so keep on reading!

Getting Started

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.

1

Creating a Drop Zone

The next thing 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 draging zone if we attempt to dump it outside the drop zone.

<div cdkDropList>
  <div class="box" cdkDrag>
    Drag me around
  </div>
</div>

2

Re-Ordering Items Inside a List

Now that we know how to drag elements around and how to create a “drop zone” to leave things in, we’ll tackle “re-ordering”. 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 an array of strings 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.

3

You’ll notice that when we drag and drop anything, it returns to its original place, we need to fix that!

We must implement the cdkDropDropped method 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 you already saw before. 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>

Now elements inside the cdkDrop container are draggable and reorderable (and they don’t fly back to the wrong place when we drop them!). Click here for a better understanding.

4

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.

replayer.png

Start enjoying your debugging experience - start using OpenReplay for free.

Dragging from one list to another list

Let’s go one step farther now and construct a simple 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:

  • add the incomingGoods to the availableGoods list;
  • add the availableGoods to the incomingGoods and soldGoods list;
  • add the 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; we need to transfer items from one list to the other. So let’s take a look at what the code for that would look like:

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

When drag & dropping an element, you have two options: you either do it within the same list or into another list. If you’re dropping the elementin inside the same list, It rearranges the elements. If on the other hand, you’re moving items from one list to the other, then the dragged item gets transferred to the list where it is being dumped. Once more, a useful function called transferArrayItem is included right out of the box. You can check the documentation to know more.

5

Disable dragging

If you want to disable the ability to drag elements around, simply 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 file, we set the boolean property disabled 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.

6

Conclusion

Angular Material is flat and extremely simple by design. It was designed for you to add new CSS rules instead of changing the existing ones. In theory, this design philosophy is meant to be simple and intuitive to use, and at the same time, offer a great deal of flexibility by allowing you to take charge when needed.

You can get more information by consulting the sourcecode and documentation.

Important resources:

A TIP FROM THE EDITOR: If you enjoyed this article, you should also take a look at Getting started with Angular Material UI for more information about getting started with AngularMaterial.