The Angular Kanban component displays work as cards arranged in workflow columns, with optional swimlanes. Use it for task boards, project planning, and workflows where users move items between stages.

This component is included in DayPilot Pro for JavaScript. It is not included in DayPilot Lite.

angular kanban component

Angular Kanban Features

  • Workflow columns. Organize work into stages such as To do, In progress, and Done. Users can move cards between columns and change their order within a column.

  • Swimlanes. Group the same workflow by team, project, or another category. Each card can identify both its column and its swimlane.

  • Custom card data. Display titles, text, colors, and application-specific fields. Use card-rendering callbacks and active areas for additional labels and actions.

  • Application integration. Load columns, cards, and swimlanes from your backend. Use the move callback to save workflow and ordering changes; the component does not supply a persistence service.

Demos and Tutorials

Each tutorial includes a downloadable project. The edition used by the example is shown beside its link.

Older Angular Projects

Configure a Starter Project

Use Kanban UI Builder to explore settings in a live preview and download an Angular starter. Continue adjusting the generated configuration in your own code.

Installation

DayPilot Pro

At npm.daypilot.org, select Angular and copy the installation command for daypilot-pro-angular. Licensed customers can obtain their package URL in the customer area.

The examples use standalone Angular components. Import DayPilotModule in the component that contains the DayPilot tag.

Display a Kanban Board

Start with two workflow columns and one card. Drag the card from To Do to Done.

kanban.component.ts

import {Component} from '@angular/core';
import {DayPilot, DayPilotModule} from 'daypilot-pro-angular';

@Component({
  selector: 'app-kanban',
  standalone: true,
  imports: [DayPilotModule],
  template: `
    <daypilot-kanban [columns]="columns" [cards]="cards"></daypilot-kanban>
  `
})
export class KanbanComponent {
  columns: DayPilot.KanbanColumnData[] = [
    {id: 'todo', name: 'To Do'},
    {id: 'done', name: 'Done'}
  ];
  cards: DayPilot.CardData[] = [
    {id: 'proposal', name: 'Proposal', text: 'Prepare proposal', column: 'todo'}
  ];
}

Import KanbanComponent in its parent and add <app-kanban></app-kanban> to the parent template. Each card has a unique id, a title in name, optional body text, and a column matching a column ID.

Add Cards and Group Them by Team

Swimlanes add horizontal groups. This board has two teams. The Add card button asks for a task name and adds it to Team A’s To Do column. Every card, including new ones, carries both its column and swimlane IDs.

kanban-teams.component.ts

import {Component, ChangeDetectorRef, inject} from '@angular/core';
import {DayPilot, DayPilotModule} from 'daypilot-pro-angular';

@Component({
  selector: 'app-kanban',
  standalone: true,
  imports: [DayPilotModule],
  template: `
    <button (click)="addCard()">Add card</button>
    <daypilot-kanban
      [columns]="columns"
      [swimlanes]="swimlanes"
      [cards]="cards"></daypilot-kanban>
  `
})
export class KanbanComponent {
  private changes = inject(ChangeDetectorRef);

  columns: DayPilot.KanbanColumnData[] = [
    {id: 'todo', name: 'To Do'}, {id: 'done', name: 'Done'}
  ];
  swimlanes: DayPilot.SwimlaneData[] = [
    {id: 'team-a', name: 'Team A'}, {id: 'team-b', name: 'Team B'}
  ];
  cards: DayPilot.CardData[] = [
    {id: 'proposal', name: 'Proposal', text: 'Prepare proposal',
      column: 'todo', swimlane: 'team-a'}
  ];

  async addCard() {
    const modal = await DayPilot.Modal.prompt('Task name:', 'New task');
    if (modal.canceled) { return; }
    this.cards = [...this.cards, {
      id: DayPilot.guid(), name: modal.result,
      column: 'todo', swimlane: 'team-a'
    }];
    this.changes.markForCheck();
  }
}

Replacing the bound cards array displays application-created cards. See column loading, card loading and swimlane loading for the data formats. The example adds cards locally; a backend application should create the record first and use the returned ID.

Save Card Placement and Order

Moving a card updates the board automatically. onCardMoved runs afterward, when the card list reflects its new order, column and swimlane. Save that complete list if your backend stores the whole board’s ordering.

kanban-saving.component.ts

import {Component, ChangeDetectorRef, inject} from '@angular/core';
import {DayPilot, DayPilotModule} from 'daypilot-pro-angular';

@Component({
  selector: 'app-kanban',
  standalone: true,
  imports: [DayPilotModule],
  template: `
    <button (click)="addCard()">Add card</button>
    <daypilot-kanban
      [config]="config"
      [columns]="columns"
      [swimlanes]="swimlanes"
      [cards]="cards"></daypilot-kanban>
  `
})
export class KanbanComponent {
  config: DayPilot.KanbanConfig = {
    onCardMoved: args => this.saveBoard(args.control.cards.list)
  };

  async saveBoard(cards: DayPilot.CardData[]) {
    try {
      await DayPilot.Http.put('/api/board/cards', cards);
    } catch {
      await DayPilot.Modal.alert('The board could not be saved.');
    }
  }

  private changes = inject(ChangeDetectorRef);

  columns: DayPilot.KanbanColumnData[] = [
    {id: 'todo', name: 'To Do'}, {id: 'done', name: 'Done'}
  ];
  swimlanes: DayPilot.SwimlaneData[] = [
    {id: 'team-a', name: 'Team A'}, {id: 'team-b', name: 'Team B'}
  ];
  cards: DayPilot.CardData[] = [
    {id: 'proposal', name: 'Proposal', text: 'Prepare proposal',
      column: 'todo', swimlane: 'team-a'}
  ];

  async addCard() {
    const modal = await DayPilot.Modal.prompt('Task name:', 'New task');
    if (modal.canceled) { return; }
    this.cards = [...this.cards, {
      id: DayPilot.guid(), name: modal.result,
      column: 'todo', swimlane: 'team-a'
    }];
    this.changes.markForCheck();
  }
}

The /api/board/cards endpoint should store array order as well as card placement. The example reports a failed save while leaving the local move visible; add retry or rollback behavior as needed. The swimlane bindings and the Add card behavior remain part of this board.

Access the Kanban API

Use the underlying control to find and update an existing card. This independent example renames the proposal without replacing the whole board.

kanban-api.component.ts

import {Component, ViewChild} from '@angular/core';
import {DayPilot, DayPilotModule, DayPilotKanbanComponent} from 'daypilot-pro-angular';

@Component({
  selector: 'app-kanban',
  standalone: true,
  imports: [DayPilotModule],
  template: `
    <button (click)="renameCard()">Rename proposal</button>
    <daypilot-kanban #kanban
      [columns]="columns"
      [swimlanes]="swimlanes"
      [cards]="cards"></daypilot-kanban>
  `
})
export class KanbanComponent {
  columns: DayPilot.KanbanColumnData[] = [
    {id: 'todo', name: 'To Do'}, {id: 'done', name: 'Done'}
  ];
  swimlanes: DayPilot.SwimlaneData[] = [
    {id: 'team-a', name: 'Team A'}, {id: 'team-b', name: 'Team B'}
  ];
  cards: DayPilot.CardData[] = [
    {id: 'proposal', name: 'Proposal', text: 'Prepare proposal',
      column: 'todo', swimlane: 'team-a'}
  ];

  @ViewChild('kanban') kanban!: DayPilotKanbanComponent;

  async renameCard() {
    const control = this.kanban.control;
    const card = control.cards.find('proposal');
    const modal = await DayPilot.Modal.prompt('Task name:', card.data.name);
    if (modal.canceled) { return; }
    card.data.name = modal.result;
    control.cards.update(card);
  }
}

Read kanban.control after the child initializes, such as in the button handler above. The wrapper manages the control’s lifecycle. See the Kanban API for methods and callbacks.

Use a Signal for Cards

If the application keeps cards in an Angular signal, pass the signal to [cards]. This variation uses update() to append the newly entered card.

kanban-signals.component.ts

import {Component, signal} from '@angular/core';
import {DayPilot, DayPilotModule} from 'daypilot-pro-angular';

@Component({
  selector: 'app-kanban',
  standalone: true,
  imports: [DayPilotModule],
  template: `
    <button (click)="addCard()">Add card</button>
    <daypilot-kanban
      [columns]="columns"
      [swimlanes]="swimlanes"
      [cards]="cards"></daypilot-kanban>
  `
})
export class KanbanComponent {
  columns: DayPilot.KanbanColumnData[] = [
    {id: 'todo', name: 'To Do'}, {id: 'done', name: 'Done'}
  ];
  swimlanes: DayPilot.SwimlaneData[] = [
    {id: 'team-a', name: 'Team A'}, {id: 'team-b', name: 'Team B'}
  ];
  cards = signal<DayPilot.CardData[]>([
    {id: 'proposal', name: 'Proposal', text: 'Prepare proposal',
      column: 'todo', swimlane: 'team-a'}
  ]);

  async addCard() {
    const modal = await DayPilot.Modal.prompt('Task name:', 'New task');
    if (modal.canceled) { return; }
    this.cards.update(cards => [...cards, {
      id: DayPilot.guid(), name: modal.result,
      column: 'todo', swimlane: 'team-a'
    }]);
  }
}

Binding [cards]="cards" lets the wrapper observe the signal. Binding cards() passes its current array through the ordinary input path. Keep one form consistently; signals require Angular 16 or later.

Availability

Availability of this feature in DayPilot editions:

ProductLitePro
DayPilot for JavaScript
DayPilot for ASP.NET WebForms
DayPilot for ASP.NET MVC
DayPilot for Java