In the past weeks, I’ve written about how to make a drag & drop list using Angular CDK and how to enable dragging & dropping between multiple lists. Trello is the app that first comes to mind when I think of how this can be used to create a great experience.
At its core, a Trello board has just three pieces of functionality: create lists, create cards within the lists, and reorganize cards within and between the lists. The articles above give us all the tools we need to do this quickly ourselves, so let’s do it!
We’ll use the Angular CLI to do the following:
- Create a new app
- Add a list component
- Add a board component
- Make it pretty
Create New App
This first step’s pretty easy. We’re just going to use ng
to create a new app and install the Angular CDK.
$ ng new lists-app --defaults
$ cd lists-app
$ npm install @angular/cdk
Do a quick check to make sure we’re starting in a good state:
$ ng serve --open

Add List Component
Now we’ll make our list component using ng generate component
.
$ ng g c list
Hop into the code and make three changes. First, we need to import the DragDrop
and Forms
modules in src/app/app.module.ts
:
import { BrowserModule } from '@angular/platform-browser';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ListComponent } from './list/list.component';
@NgModule({
declarations: [
AppComponent,
ListComponent
],
imports: [
BrowserModule,
DragDropModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Next, update src/app/list/list.component.ts
to support drag/drop and dynamic creation of items. We’ll add the items
array to store our list items, a drop
function to handle our drop/drop event, and an onSubmit
function to add new items to our list.
import { Component, OnInit } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { NgForm } from '@angular/forms';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
items: string[] = [];
constructor() { }
ngOnInit(): void {
}
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);
}
}
onSubmit(newItemForm: NgForm) {
this.items.push(newItemForm.value.newItem);
newItemForm.reset();
}
}
The third step is to change our markup in src/app/list/list.component.html
. This is just two parts, displaying the drag-&-droppable list items and accepting input for new items.
<div cdkDropList [cdkDropListData]="items" (cdkDropListDropped)="drop($event)">
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
</div>
<form #newItemForm="ngForm" (ngSubmit)="onSubmit(newItemForm)">
<input name="newItem" ngModel type="text" placeholder="Enter a new item"><button type="submit">Add Item</button>
</form>
That’s it for our list component, but let’s make one more change to app/app.component.html
so we can test. Replace its entire contents with the following:
<app-list></app-list>
Now let’s do another check-in. We should be able to add items to our list and move them around via drag & drop.
$ ng serve --open

Add Board Component
Once again, we look to ng generate component
to create our board
component.
$ ng g c board
Just like the list
component allows for dynamic creation of list items, we want our board
component to allow dynamic creation of lists. So, let’s modify src/app/board/board.component.ts
to support this:
import { Component, OnInit } from '@angular/core';
import { ListComponent } from '../list/list.component';
@Component({
selector: 'app-board',
templateUrl: './board.component.html',
styleUrls: ['./board.component.css']
})
export class BoardComponent implements OnInit {
lists: ListComponent[] = [];
constructor() { }
ngOnInit(): void {
}
addList() {
var newList = new ListComponent();
this.lists.push(newList);
}
}
And make the markup changes in src/app/board/board.component.html
. One thing to note is the use of cdkDropListGroup
. This makes all the lists connected and allows dragging & dropping between them.
<button (click)="addList()">Add List</button>
<div class="list-container" cdkDropListGroup>
<app-list *ngFor="let list of lists"></app-list>
</div>
We’ll also modify src/app/board/board.component.css
so that lists will be added horizontally.
.list-container {
display: flex;
flex-direction: row;
}
Finally, we’ll update app.component.html
to use our board
component instead of a single list
:
<app-board></app-board>
Our board
component is complete, so let’s do another check-in.
$ ng serve --open

Make It Pretty
At this point, the hard part’s done. We have our core functionality implemented. We can add lists, add items to the lists, and move the items around. Now let’s make it look nice!
Begin by installing Angular Material:
$ ng add @angular/material
Then import the MatCard
, MatButton
, and MatInput
modules in src/app.module.ts
:
import { BrowserModule } from '@angular/platform-browser';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ListComponent } from './list/list.component';
import { BoardComponent } from './board/board.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
declarations: [
AppComponent,
ListComponent,
BoardComponent
],
imports: [
BrowserModule,
DragDropModule,
FormsModule,
BrowserAnimationsModule,
MatButtonModule,
MatCardModule,
MatInputModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now we can use mat-card
, mat-raised-button
, and mat-form-field
in src/app/list/list.component.html
:
<div class="container">
<div class="list" cdkDropList [cdkDropListData]="items" (cdkDropListDropped)="drop($event)">
<mat-card class="list-item" *ngFor="let item of items" cdkDrag>
<mat-card-content>
<p>{{item}}</p>
</mat-card-content>
</mat-card>
</div>
<form #newItemForm="ngForm" (ngSubmit)="onSubmit(newItemForm)">
<mat-form-field>
<input matInput name="newItem" ngModel type="text" placeholder="Enter a new item">
</mat-form-field>
<button mat-raised-button type="submit" color="accent">Add Item</button>
</form>
</div>
And we’ll add a little CSS to src/app/list/list.component.css
:
.container {
margin-right: 10px;
}
.container button {
margin-left: 5px;
}
.list {
padding: 10px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: #fafafa;
border-radius: 4px;
overflow: hidden;
}
.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);
}
.list-item {
margin: 5px;
}
.list-item:last-child {
border: none;
}
Then we’ll do some similar things to src/app/board/board.component.html
and board.component.css
:
<button (click)="addList()" mat-raised-button color="primary">Add List</button>
<div class="list-container" cdkDropListGroup>
<app-list *ngFor="let list of lists"></app-list>
</div>
button {
margin: 10px;
}
.list-container {
display: flex;
flex-direction: row;
margin: 10px;
}
Now we’ve got fancy buttons, some input animations, and shadows while dragging, and it looks quite nice!

That’s where we’ll leave it today. The code I wrote while constructing this post can be found here.