Create a kanban board with Angular 4

In this tutorial, We will be creating a simple kanban board with Angular 4. A Kanban board is a work and workflow visualization tool that enables you to optimize the flow of your work, a basic Kanban board has a three-step workflow: To Do, In Progress, and Done.

Live Demo

We represent every work item as a separate card on the board to allow us to track the progress of work through the workflow in a highly visual manner. For this tutorial i am using Angular Ide Found here https://www.genuitec.com/products/angular-ide/ , though you can use whatever editor you prefer.
Let’s get started by opening up Angular IDE, and create a new project SimpleBoard.


Taking a look at the Kanban board image above we can identify two visual components namely lists and cards, a third component not visible is the board component. To create a component, we right click on ‘app’ directory in ‘src’, select New > Component. Let us create BoardComponent

Next, we edit the files created by Angular Ide as follows.

src/app/board/board.component.ts

import { Component, OnInit } from '@angular/core';
import { CardStore } from '../CardStore';
import { ListSchema } from '../ListSchema';

@Component({
  selector: 'app-board',
  templateUrl: './board.component.html',
  styleUrls: ['./board.component.css']
})
export class BoardComponent implements OnInit {
  cardStore: CardStore;
  lists: ListSchema[];
  constructor() { }
  setMockData(): void {
    this.cardStore = new CardStore();
    const lists: ListSchema[] = [
      {
        name: 'To Do',
        cards: []
      },
      {
        name: 'Doing',
        cards: []
      },
      {
        name: 'Done',
        cards: []
      }
    ]
    this.lists = lists;
  }

  ngOnInit() {
    this.setMockData();
  }

}

/src/app/board/board.component.html

< div>
  <app-list *ngFor="let list of lists" [list]="list" [cardStore]="cardStore"></app-list>
< /div>

src/app/board/board.component.css

div {
    background: #ffffff;
    display: flex;
    padding: 0 5px;
    height: 100vh;
    overflow-x: scroll;
}

We need to add three more files
src/app/cardschema.ts

export class CardSchema {
  id: string;
  description: string;
}

src/app/cardstore.ts

import {CardSchema} from './cardschema';

export class CardStore {
  cards: Object = {};
  lastid = -1;
  _addCard(card: CardSchema) {
    card.id = String(++this.lastid);
    this.cards[card.id] = card;
    return (card.id);
  }

  getCard(cardId: string) {
    return this.cards[cardId];
  }

  newCard(description: string): string {
   const card = new CardSchema();
   card.description = description;
   return (this._addCard(card));
  }
}

src/app/listschema.ts

export class ListSchema {
  name: string;
  cards: string[];
}

CardSchema is the class from which every card instance will be created, we use CardStore to maintain a collection of cards and it will be used as datastore of sort (in subsequent articles we will connect the kanban board to a backend), finally ListSchema is used to create instances of a lists of cards. ListSchema has only two attributes, the name of the list which is a string and the cards in the list which is an array (it could be better represented by a linked list, but an array will do for now).

Next, we create ListComponent and CardComponent with the following content

src/app/card/card.component.css

p {
    background: white;
    margin: 0 0 6px 0;
    padding: 6px 6px 2px 8px;
}

src/app/card/card.component.html

< p class="card" draggable="true" (dragstart)="dragStart($event)" id="{{card.id}}">
 {{card.description}}
< /p>

src/app/card/card.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { CardSchema } from '../cardschema';

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
  @Input() card: CardSchema;
  constructor() { }

  ngOnInit() {
  }

  dragStart(ev) {
    ev.dataTransfer.setData('text', ev.target.id);
  }

}

src/app/list/list.component.css

.list {
    background: #e2e4e6;
    width: 258px;
    padding: 6px;
    margin: 5px;
    display: inline-block;

}
.list__title {
    margin: 0;
    padding: 16px 0;
}
.list a {
    width: 100%;
    display: block;
    text-decoration: none;
}

input{
  width: 248px;
  padding: 5px;
  border: 2px solid orange;
  outline: 0;
  background: #fff;
  box-shadow:none;
}

src/app/list/list.component.html

< div class="list" (dragover)="allowDrop($event)" (drop)="drop($event)">
    < p class="list__title"><strong>{{list.name}}</strong></p>
    < div class="cards">
        <app-card *ngFor="let cardId of list.cards" [card]="cardStore.getCard(cardId)"></app-card>
    < /div>

    < input #addCardInput type="text" (keyup.enter)="onEnter(addCardInput.value); addCardInput.value=''; displayAddCard=false;" *ngIf="displayAddCard" autofocus>
    <a href="#" class="list__newcard" (click)="toggleDisplayAddCard();">Add a card...</a>
</ div>

src/app/list/list.component.ts

import { Component, HostListener, Input, OnInit } from '@angular/core';
import { CardSchema } from '../CardSchema';
import { ListSchema } from '../ListSchema';
import { CardStore } from '../CardStore';

@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
  @Input() list: ListSchema;
  @Input() cardStore: CardStore;
  displayAddCard = false;

  constructor() { }
  toggleDisplayAddCard() {
    this.displayAddCard = ! this.displayAddCard;
  }
  ngOnInit(): void {
  }

  allowDrop($event) {
    $event.preventDefault();
  }

  drop($event) {
    $event.preventDefault();
    const data = $event.dataTransfer.getData('text');

    let target = $event.target;
    const targetClassName = target.className;

    while( target.className !== 'list') {
      target = target.parentNode;
    }
    target = target.querySelector('.cards');

    if(targetClassName === 'card') {
      $event.target.parentNode.insertBefore(document.getElementById(data), $event.target);
    } else if(targetClassName === 'list__title') {
      if (target.children.length) {
        target.insertBefore(document.getElementById(data), target.children[0]);
      }else {
        target.appendChild(document.getElementById(data));
      }
    } else {
      target.appendChild(document.getElementById(data));
    }

  }

  onEnter(value: string) {
    const cardId =  this.cardStore.newCard(value);
    this.list.cards.push(cardId);
  }
}

Code review

Now we have a working kanban board.

kanban board

so I’ll discuss some parts, please comment below if you want us to talk about anything else.

src/app/card/card.component.html

In src/app/card/card.component.html we set the draggable HTML attribute of the p element to true which makes it possible for us to drag and drop an HTML element. In HTML5, drag, and drop is part of the standard: Any element can be draggable.
Also, we call a method dragStart when the dragstart event is triggered on the p element.

src/app/card/card.component.ts

The DataTransfer object is used to hold the data that is being dragged during a drag and drop operation. It may hold one or more data items, each of one or more data types. This object is available from the dataTransfer property of all drag events. It cannot be created separately (i.e. there is no constructor for this object).

src/app/list/list.component.ts

On drop, we add a new node to the dom.

Conclusion

I would like some questions and contributions so I am keeping it brief. In a followup article, we will connect our board to a Django backend for persistence. Do leave a reaction or comment below.

Also, Read create a dashboard for an Ecommerce store with Angular 4.

About the author

Gbekeloluwa Simeon Olufotebi

I'm Gbekeloluwa Olufotebi and I am a product manager. I also design user interfaces and code often. I love python and I am adept at Fullstack Javascript. Vue is my favorite frontend framework but I also contribute to ember, react and angular projects.

Pin It on Pinterest

Shares

Get the best in web dev

Join dunebook.com and recieve best in web dev , once a week FREE

An email has been Sent to your Inbox ! Please Confirm your Subscription :)