I've been doing full stack development since the late 90's, this was before full stack development even had a moniker. The front end screens I developed were always simple by design, sticking with the KISS(Keep it simple, stupid!) principle. Add a grid of data here, add some input forms there, and a couple buttons once the user is ready to submit the data. Back then, we were using VB6 with the
WYSIWYG(what you see is what you get) functionality of the editor. Things are different with web front ends and web frameworks to render the front end. When a project I was on recently had the requirement of some fancy drag and drop of complex UI elements with Angular as the front end I knew I might be up for some learning and some challenging coding. In this article I'll describe the Angular Component Development Kit's drag and drop functionality and show some customizations that can be done to add advanced functionality that doesn't come out of the box with the framework.
The Angular Component Development Kit (CDK) provides excellent support for adding drag and drop features to your UI. The CDK provides built in support for free dragging, sorting items in a list, and transferring items between two arrays. Often times in UI development it gets more complicated and requires custom TypeScript code in addition to the built in features to achieve the results the users are looking for. I had a client who had a request for free drag and drop of a tree like structure that required more advanced coding than just the Angular examples.
With the help of the Angular CDK, taking advantage of the drag and drop API, and some plain old DOM manipulation we were able to provide the end users with what they were looking for with their requirements for the project. In this article I'll describe a similar scenario and what the Angular code looks like to accomplish this.
Basic Requirements
The client I had for this project had much more complicated models and requirements which are beyond the scope of this blog. For the purpose of a demonstration let's assume these are the simple requirements. The UI displays bank branch info and their employees in that branch. The users would like to drag and drop employees from one branch to another. The users would also like some visual cues as to where the drop target is. This will be done by highlighting the drop branch in white. So in this case there will be n number of branches along with x number of employees in each branch. Most of the examples out there are simply dragging an item from one list to another, but not more than two lists. Also, there is no native support to have the target drop area have a different visual style so that the user has an indication of where they are dropping it. I will show you how to accomplish both of these below.
Typescript Models
First things first. We want to model our real world subject or thing with a class in TypeScript. In this case we want a simple model for the Branch. The Branch has a name, id, and an string array of employee names. The constructor keyword and pattern are added to help consumers initialize the Branch object.
export class Branch { name: string; id: string; employees: string[]; constructor(n: string, id: string, es: string[]) { this.name = n; this.id = id; Â Â Â Â Â Â Â Â this.employees = es;
} }
Markup
<div> <div class="branches" style="display: flex;"> <div cdkDropListGroup style="display: flex;"> <div class="branch-border" *ngFor="let branch of branches"> <div class="branch" id={{branch.id}} cdkDropList (cdkDropListDropped)="drop($event)" [cdkDropListData]="branch.employees"> <div class="branch-title">{{branch.name}}</div> <div [cdkDragData]="employee" *ngFor="let employee of branch.employees" class="employee" cdkDrag (cdkDragMoved)="dragMoved($event)"> {{employee}} </div> </div> </div> </div> </div> </div>
As you can see the main screen is not that much code to display n branches and x number of employees in each branch. The nested *ngFor directive for the branches and their employees makes this possible which is a powerful capability of Angular itself.
Now for what makes the drag and drop possible:
cdkDrag this directive is what makes an element draggable. In this case we want to be able to drag an employee's name over to another branch and drop it. So we've added the cdkDrag on the employee div. You must also import the CdkDragDrop module into the module where you want to use it. In this case we'll add that to the app.component.ts.
cdkDragMoved is the event that gets triggered when the element starts to move. In this case we want to apply some different styling to the branch when the employee is being moved over it. This is part of the custom TypeScript code that I wrote to accomplish styling the target element so that the user can see where they are dropping the item.
cdkDropList allows the element to be a drop target of a drag and drop. In this case the entire branch div can receive a drop.
cdkDropListDropped is the event that gets fired when an employee is dropped. This is what will actually move the employee from one branch to another.
cdkDropListData This is what data binds the specific array to the UI element. Later below this is what will come thru in the drop() method attached to the event object.
cdkDropListGroup Is the directive that ties all the branches employees list together and drop targets and really what makes this whole example work.
cdkDropListData The directive is data bound to the branch.employees property in the *ngFor loop and makes data binding and the UI code simple and elegant.
Typescript
Initializing the branches
constructor(@Inject(DOCUMENT) private document: Document) { } branches = [ new Branch("Branch 1", "1", [this.genRandomName(), this.genRandomName(), this.genRandomName(), this.genRandomName()]), new Branch("Branch 2", "2", [this.genRandomName(), this.genRandomName(), this.genRandomName(), this.genRandomName()]), new Branch("Branch 3", "3", [this.genRandomName(), this.genRandomName(), this.genRandomName(), this.genRandomName()]), ];
In the constructor of the app component we will create 3 branches with 4 employees each. The genRandomName() is a private method that will return a random person's first name.
Handling the drop
drop(event: CdkDragDrop<any>) { 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, ); } this.clearDragInfo(); }
The drop() method is what we wired up in the template code to fire on the cdkDropListDropped event. This is where the employee will actually get moved to the different branch. We can use the Angular's moveItemInArray() and transferArrayItem() to accomplish this. When the previous container and the drop container are the same that means the user dragged and dropped within the same branch in which case we can just re-order the array. This is what the moveItemInArray() function does. When the drop container and previous container are different that means the user dragged the employee name to another branch. In this case we want to use the transferArrayItem() function which will actually move the item from one array to another array at the specified index.
Highlighting the target element
In the project we did for a client, the users wanted some visual cue as to what target element the drag object was being moved to. There doesn't seem to be any functionality in the Angular CDK to apply a different style to the target element. In the case of this example, we want the branch being dragged to be highlighted so that they can tell for sure where the employee will land. In a simple example like this, maybe it's not absolutely necessary since there's only 3 branches. But in a complex UI such as the one we did it was helpful to see a visual indicator with the complex data models and UI elements that were on the screen.
We did this by adding custom code to the cdkDragMoved event. When an element gets moved, this event is fired in the TypeScript code. Wired up like this in the html:
<div [cdkDragData]="employee" *ngFor="let employee of branch.employees" class="employee" cdkDrag (cdkDragMoved)="dragMoved($event)"> {{employee}} </div>
In the event sent to the TypeScript code we can capture the x and y coordinates, and use elementsFromPoint() of the DOM document to find where the cursor is and thus find which element to highlight. The DOM document needs to be injected into the constructor of the component before we can use the document object.
constructor(@Inject(DOCUMENT) private document: Document) { }
dragMoved(event: any) { // find the DOM element that the user dropped the the item on. Use the event x, y coordinates and find // the closest branch element. let target = this.notSelf(this.document.elementsFromPoint(event.pointerPosition.x, event.pointerPosition.y)).closest(".branch"); if (!target) { this.clearDragInfo(); return; } this.clearDragInfo(); if (target.classList.contains("branch")) { target.classList.add('branch-drag'); } else { this.clearDragInfo(); } }
Unfortunately, the implementation of the elementsFromPoint() function actually returns itself as the first closest element. So using this approach, we actually start detecting the employees current branch info on the start of the drag. To work around this problem we can write a help method to exclude the employees' own elements. The notSelf() method simply ignores all the .employee elements so that it does not pick up itself in the dragMoved event.
notSelf(elements: Element[]): Element { for (let i = 0; i < elements.length; i++) { if (!elements[i].classList.contains("employee")) { return elements[i]; } } return elements[0]; }
Once we find the appropriate branch element we can simple add a branch-drag class to this which will allow us to apply a white border giving the user the indication that this is the drop target.
Once the drop is completed, we simple call the clearDragInfo() which will remove all the instances of the branch-drag class.
clearDragInfo() {
this.document
.querySelectorAll(".branch-drag")
.forEach(element => element.classList.remove("branch-drag"));
}
Styling
Style the branch card with a flexbox and width and height of 100% so it fills up it's allowed space.
.branch { width: 100%; height: 100%; border: solid 5px #ccc; color: rgba(0, 0, 0, 0.87); background: #3d3b3b; border-radius: 4px; border-color: rgb(20, 125, 200); font-family: poppins-semibold, poppins, sans-serif; margin: auto; text-align: center; }
Employees get their own size and color.
.employee { background-color: rgb(20, 125, 200); border-radius: 500px; width: 100px; margin: 10px; align-content: center; }
The .cdk-drag-preview is what the Angular framework applies to the elements placeholder to show a preview of the drag. In this case we show a box-shadow to have the previewed element stand out to the user.
.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); }
.branch-drag is the custom class we add when the employee being dragged over a branch to show what branch they would be dropped to.
.branch-drag { border-color: whitesmoke; }
Feel free to experiment and fiddle with this demo on stackblitz and let me know if you have comments or questions.

Summary
The Angular Component Development Kit has excellent support and functionality for many things with drag and drop being one of them. Of course there are times when there is some requirement that can't be met with the out-of-the-box functionality. As the saying goes "There's more than one way to skin a cat", and most other customization can be done with the TypeScript behind the scenes. Angular and the Angular Component Development Kit is a great choice when choosing a UI for your project or modernization project. Let us know if we can use our experience and skills for your software and technology needs.
If you have any questions on this or other topics, please feel free to contact me:
About the author:
I am a full stack developer with 20+ years experience in the financial services space building applications with Angular, .NET, Entity Framework, Web API, WCF, ASP.NET, ASP.NET MVC, SQL Server, GoLang, Postgres, AWS and Azure.
Comments