Bob at Genuitec Virtual evangelist at large. The face of Genuitec often appearing in graphics. Pen-name for our more shy writers of content.

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, though you can use whatever editor you prefer.

Let’s get started by opening up Angular IDE, and creating a new project SimpleBoard .







Taking a look at the Kanban board image above, we can identify two visual components, namely Lists and Cards, but a third component is not visible. 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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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

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

src/app/board/board.component.css

1 2 3 4 5 6 7 div { background : # ffffff ; display : flex ; padding : 0 5px ; height : 100vh ; overflow - x : scroll ; }

We need to add three more files:

src/app/cardschema.ts

1 2 3 4 export class CardSchema { id : string ; description : string ; }

src/app/cardstore.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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

1 2 3 4 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 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

1 2 3 4 5 p { background : white ; margin : 0 0 6px 0 ; padding : 6px 6px 2px 8px ; }

src/app/card/card.component.html

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

src/app/card/card.component.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 . 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

1 2 3 4 5 6 7 8 9 < 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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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.



So I’ll discuss some parts.

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

In a follow-up article, we will connect our Kanban board to a Django backend for persistence.

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

Ready to use Angular IDE?