
Syncfusion Angular Kanban
- 192 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Use syncfusion-angular-kanban for development tasks
About
syncfusion-angular-kanban: A skill for development. This provides functionality for development workflows.
- syncfusion-angular-kanban
Syncfusion Angular Kanban by the numbers
- 192 all-time installs (skills.sh)
- +11 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,106 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-kanbanAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 192 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Use syncfusion-angular-kanban for development tasks
Files
Implementing Syncfusion Angular Kanban
When to Use This Skill
The Syncfusion Angular Kanban component is essential when you need to:
- Visualize workflows - Display task stages (To Do → In Progress → Testing → Done)
- Enable task management - Allow users to organize and track work across multiple columns
- Implement drag-and-drop - Let users move cards between columns to change task status
- Group tasks - Use swimlanes to organize cards by owner, priority, or category
- Set constraints - Enforce WIP (Work In Progress) limits with min/max card counts
- Customize appearance - Apply templates, themes, and custom styling to cards and columns
- Handle events - Listen to card clicks, drag operations, and state changes
- Persist state - Save board configuration and card positions
Component Overview
The Kanban board is a visual workflow management tool built with:
- Cards - Represent individual tasks (mapped to dataSource via
cardSettings) - Columns - Define workflow stages (mapped using
keyFieldand column configuration) - Swimlanes - Group cards by category (employee, priority, etc.)
- Drag-and-drop - Enabled by default (
allowDragAndDrop: true) - Data Binding - Supports local arrays and remote DataManager
- Templates - Customize card, column header, swimlane header, and tooltip rendering
- Responsive - Adapts layout to desktop and mobile devices
- Themes - Five built-in themes: Material, Bootstrap 3, Bootstrap 4, Fabric, High Contrast
Key Features
✓ Multi-column workflow with customizable headers ✓ Smooth drag-and-drop between columns and within columns ✓ Swimlane grouping by any data field ✓ WIP validation (min/max card counts per column/swimlane) ✓ Templates for cards, columns, swimlanes, dialogs, and tooltips ✓ Local & remote data binding with DataManager integration ✓ Keyboard navigation and full accessibility support ✓ Events for user interactions (click, drag, selection, dialog open/close) ✓ Methods for programmatic control (add/delete cards, show/hide columns) ✓ Multiple selection modes (single or multiple card selection) ✓ Stacked headers for grouping related columns ✓ Built-in themes with CSS customization support
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
- Installation and npm package setup
- Angular module imports and configuration
- CSS imports and theme registration
- Basic Kanban initialization with minimal example
- First running board with sample data
Core Structure: Columns and Swimlanes
📄 Read: references/columns-and-swimlanes.md
- Column configuration with keyField mapping
- Single-key vs. multi-key column mapping
- Column headers and stacked headers
- Swimlane grouping by field
- Dynamic column add/remove/show/hide
Cards and Data Binding
📄 Read: references/cards-and-data-binding.md
- Card structure: header field, content field, grabber field
- Card templates and custom rendering
- Local array data binding
- Remote data binding with DataManager
- OData v4 support
- Card selection modes (single/multiple)
Drag-and-Drop and Interaction
📄 Read: references/drag-and-drop.md
- Drag-and-drop functionality (enabled by default)
- Card movement between columns
- Reordering within columns
- transitionColumns to control valid transitions
- Drag events (beforeDragStart, dragStart, dragStop)
- WIP limits: enforcing min/max card counts
- Keyboard navigation support
Features and Customization
📄 Read: references/features-and-customization.md
- Sorting, filtering, and searching cards
- Validation rules and dialogs
- Responsive mode for mobile
- Custom dialog templates
- Tooltip templates and configuration
- Priority indicators and color grabbers
- Column toggling and collapsing
Properties Reference
📄 Read: references/properties.md
- Core properties: allowDragAndDrop, allowKeyboard, allowSelection
- Card configuration: cardSettings (headerField, contentField, template, etc.)
- Column setup: columns array with ColumnsModel
- Swimlane configuration: swimlaneSettings
- Dialog and tooltip templates
- WIP limits: minCount, maxCount per column
- Responsive breakpoints and heights
Methods Reference
📄 Read: references/methods.md
- Card operations: addCard(), deleteCard(), updateCard(), getSelectedCards()
- Column operations: addColumn(), removeColumn(), hideColumn(), showColumn()
- Board operations: refresh(), getCardCount()
- Swimlane operations: getSwimlaneData()
- Dialog control: openDialog(), closeDialog()
- Search/Filter: search(), filter(), sortBy()
Events Reference
📄 Read: references/events.md
- Action events: actionBegin, actionComplete, actionFailure
- Card events: cardClick, cardDoubleClick, cardSelection
- Drag events: dragStart, dragStop, beforeDragStart
- Dialog events: dialogOpen, dialogClose
- Column/swimlane events: columnClick, swimlaneClick
- Event argument types and data structures
Data Persistence and State
📄 Read: references/data-persistence.md
- Local storage persistence for board state
- Saving card positions and column state
- ObservableCollection integration
- State restoration after page reload
- Manual state save/restore patterns
Styling, Themes, and Appearance
📄 Read: references/styling-and-theming.md
- Built-in themes (Material, Bootstrap 3/4, Fabric, High Contrast)
- CSS variable customization
- Card and column custom styling
- Dark mode support
- RTL (right-to-left) language support
- Theme Studio integration
Accessibility and Internationalization
📄 Read: references/accessibility-and-localization.md
- WCAG compliance and keyboard navigation
- ARIA attributes for screen readers
- Focus management and keyboard shortcuts
- Internationalization (i18n) setup
- Multi-language support
- Locale data configuration
Migration and Best Practices
📄 Read: references/migration-and-best-practices.md
- EJ1 to EJ2 migration guide
- Performance optimization strategies
- Virtual scrolling for large datasets
- Common patterns and code examples
- Troubleshooting common issues
- FAQ and gotchas
Quick Start Example
1. Install Package
npm install @syncfusion/ej2-angular-kanban2. Import and Setup Component
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban-board',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[cardSettings]="cardSettings">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Testing" keyField="Testing"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanBoardComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Analyze the new requirements gathered from the customer.', Type: 'Epic' },
{ Id: 2, Status: 'InProgress', Summary: 'Improve application performance', Type: 'Epic' },
{ Id: 3, Status: 'Testing', Summary: 'Fix the issues reported in the IE browser', Type: 'Bug' },
{ Id: 4, Status: 'Close', Summary: 'Fix the issue reported by the customer.', Type: 'Bug' }
];
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary'
};
}3. Add Styles
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-kanban/styles/material3.css';Common Patterns
Pattern 1: Enable Swimlanes by Owner
Group cards by assigned team member:
swimlaneSettings: { keyField: 'Assignee' }Pattern 2: Enforce WIP Limits
Set min/max cards per column:
columns: [
{ headerText: 'To Do', keyField: 'Open', minCount: 1, maxCount: 5 },
{ headerText: 'In Progress', keyField: 'InProgress', minCount: 0, maxCount: 3 }
]Pattern 3: Handle Card Click Events
Listen to card interactions:
onCardClick(args: CardClickEventArgs): void {
console.log('Card clicked:', args.data);
}Pattern 4: Programmatically Add Cards
Use the method API:
@ViewChild('kanban') kanban: KanbanComponent;
addNewCard(): void {
this.kanban.addCard({
Id: 5,
Status: 'Open',
Summary: 'New task'
});
}Key Props Reference
| Prop | Type | Purpose |
|---|---|---|
keyField | string | Maps cards to columns based on field value |
dataSource | Object[] \ | DataManager |
cardSettings | CardSettingsModel | Header, content, and template field mappings |
columns | ColumnsModel[] | Column definitions with headers and key fields |
swimlaneSettings | SwimlaneSettingsModel | Swimlane grouping by field |
allowDragAndDrop | boolean | Enable card drag-and-drop (default: true) |
allowKeyboard | boolean | Enable keyboard navigation (default: true) |
allowSelection | boolean | Allow card selection |
selectionType | 'Single' \ | 'Multiple' |
height | string | Board height |
width | string | Board width |
Common Use Cases
1. Project Management - Track tasks from backlog to completion 2. Bug Tracking - Organize issues by status (Open, In Progress, Testing, Closed) 3. Customer Support - Manage tickets through resolution pipeline 4. Agile Sprint Board - Display user stories across sprint stages 5. Sales Pipeline - Track leads from prospect to customer 6. Content Workflow - Manage articles from draft to published 7. Recruitment - Organize candidates through hiring stages 8. DevOps Pipeline - Track deployments across environments
---
Next Steps: Choose a reference file from the navigation guide above based on your immediate need, or start with Getting Started to set up your first Kanban board.
Accessibility and Localization in Angular Kanban
Table of Contents
- Accessibility (WCAG)
- Keyboard Navigation
- ARIA Attributes
- Screen Reader Support
- Internationalization (i18n)
- Multi-Language Support
- Locale Data
Accessibility (WCAG)
WCAG 2.1 Compliance
Syncfusion Kanban supports WCAG 2.1 Level AA standards:
- ✓ Perceivable - Content is perceivable to all users
- ✓ Operable - Component can be operated via keyboard
- ✓ Understandable - Clear labels and structure
- ✓ Robust - Works with assistive technologies
Enable Accessibility
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban-accessible',
standalone: true,
imports: [KanbanModule],
template: `
<div role="main" aria-label="Project management kanban board">
<h1>Task Board</h1>
<ejs-kanban
keyField="Status"
[dataSource]="data"
[allowKeyboard]="true"
[cardSettings]="cardSettings"
tabindex="0"
role="region"
aria-label="Kanban board with columns and cards">
<e-columns>
<e-column
headerText="To Do"
keyField="Open"
role="region"
aria-label="To do column with {{ getColumnCardCount('Open') }} cards">
</e-column>
<e-column
headerText="In Progress"
keyField="InProgress"
role="region"
aria-label="In progress column with {{ getColumnCardCount('InProgress') }} cards">
</e-column>
<e-column
headerText="Done"
keyField="Close"
role="region"
aria-label="Completed column with {{ getColumnCardCount('Close') }} cards">
</e-column>
</e-columns>
</ejs-kanban>
</div>
`,
styles: [`
/* Focus indicators for keyboard navigation */
:host ::ng-deep .e-kanban .e-card:focus,
:host ::ng-deep .e-kanban .e-card:focus-visible {
outline: 3px solid #4A90E2;
outline-offset: 2px;
}
`]
})
export class KanbanAccessibleComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Design UI mockup' },
{ Id: 2, Status: 'InProgress', Summary: 'Implement authentication' }
];
cardSettings = {
headerField: 'Id',
contentField: 'Summary'
};
getColumnCardCount(status: string): number {
return this.data.filter(d => d.Status === status).length;
}
}Keyboard Navigation
Supported Keyboard Shortcuts
| Key | Action |
|---|---|
| Tab | Navigate to next card |
| Shift + Tab | Navigate to previous card |
| Arrow Keys | Move focus within board |
| Space | Select/deselect card |
| Enter | Open card details or dialog |
| Delete | Delete selected card |
| Escape | Close dialog or deselect |
Keyboard Navigation Example
@Component({
selector: 'app-kanban-keyboard',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
[allowKeyboard]="true"
keyField="Status"
[cardSettings]="{selectionType: 'Multiple'}"
(created)="onCreated()">
</ejs-kanban>
`
})
export class KanbanKeyboardComponent {
onCreated(): void {
// Configure custom keyboard shortcuts
console.log('Keyboard navigation enabled');
console.log('Use Tab to navigate, Space to select, Enter to edit');
}
}Custom Keyboard Handlers
import { Component, HostListener } from '@angular/core';
@Component({
selector: 'app-kanban-custom-keys',
standalone: true,
template: `<ejs-kanban #kanban></ejs-kanban>`
})
export class KanbanCustomKeysComponent {
@HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent): void {
if (event.ctrlKey || event.metaKey) {
switch (event.key) {
case 's':
event.preventDefault();
this.saveBoard();
break;
case 'z':
event.preventDefault();
this.undo();
break;
case 'y':
event.preventDefault();
this.redo();
break;
}
}
}
private saveBoard(): void {
console.log('Board saved');
}
private undo(): void {
console.log('Undo action');
}
private redo(): void {
console.log('Redo action');
}
}ARIA Attributes
Add ARIA Labels and Roles
import { Component } from '@angular/core';
@Component({
selector: 'app-kanban-aria',
standalone: true,
template: `
<div role="main" aria-label="Task management kanban">
<h1 id="kanban-title">Project Kanban Board</h1>
<ejs-kanban
keyField="Status"
aria-labelledby="kanban-title"
role="region"
aria-description="Drag and drop cards to organize tasks by status">
</ejs-kanban>
</div>
`
})
export class KanbanAriaComponent {}Card ARIA Attributes
cardSettings = {
template: `
<div
role="article"
aria-label="Task \${data.Id}: \${data.Summary} - Status: \${data.Status}"
tabindex="0">
<div role="heading" aria-level="3">\${data.Id}</div>
<p>\${data.Summary}</p>
<div aria-label="Priority: \${data.Priority}">\${data.Priority}</div>
</div>
`
};Screen Reader Support
Enable for Screen Readers
@Component({
selector: 'app-kanban-screenreader',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[cardSettings]="cardSettings"
role="region"
aria-live="polite"
(dragStop)="onDragStop($event)">
</ejs-kanban>
`
})
export class KanbanScreenReaderComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Design UI', Priority: 'High' }
];
cardSettings = {
headerField: 'Id',
contentField: 'Summary',
template: `
<article role="article">
<h4 aria-label="Card ID">\${data.Id}</h4>
<p aria-label="Task Description">\${data.Summary}</p>
<span aria-label="Priority Level">\${data.Priority}</span>
</article>
`
};
onDragStop(args: any): void {
// Announce card movement to screen readers
const announcement = `Card \${args.data.Id} moved to \${args.data.Status} column`;
this.announceToScreenReader(announcement);
}
private announceToScreenReader(message: string): void {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.textContent = message;
announcement.style.position = 'absolute';
announcement.style.left = '-10000px';
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
}
}Internationalization (i18n)
Setup i18n in Angular
ng add @angular/localize
ng extract-i18nLocalize Component Text
import { Component } from '@angular/core';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-kanban-i18n',
standalone: true,
imports: [KanbanModule, TranslateModule],
template: `
<select (change)="onLanguageChange($event)">
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
</select>
<ejs-kanban
keyField="Status"
[dataSource]="data">
<e-columns>
<e-column
[headerText]="'TODO' | translate"
keyField="Open">
</e-column>
<e-column
[headerText]="'IN_PROGRESS' | translate"
keyField="InProgress">
</e-column>
<e-column
[headerText]="'DONE' | translate"
keyField="Close">
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanI18nComponent {
data = [{ Id: 1, Status: 'Open', Summary: 'Task 1' }];
constructor(private translate: TranslateService) {
this.translate.setDefaultLanguage('en');
}
onLanguageChange(event: any): void {
this.translate.use(event.target.value);
}
}Multi-Language Support
Translation Files
en.json
{
"TODO": "To Do",
"IN_PROGRESS": "In Progress",
"DONE": "Done",
"ADD_CARD": "Add Card",
"DELETE_CARD": "Delete Card",
"PRIORITY": "Priority",
"ASSIGNEE": "Assignee"
}es.json
{
"TODO": "Por hacer",
"IN_PROGRESS": "En progreso",
"DONE": "Completado",
"ADD_CARD": "Agregar tarjeta",
"DELETE_CARD": "Eliminar tarjeta",
"PRIORITY": "Prioridad",
"ASSIGNEE": "Asignado a"
}fr.json
{
"TODO": "À faire",
"IN_PROGRESS": "En cours",
"DONE": "Terminé",
"ADD_CARD": "Ajouter une carte",
"DELETE_CARD": "Supprimer la carte",
"PRIORITY": "Priorité",
"ASSIGNEE": "Assigné à"
}de.json
{
"TODO": "Zu erledigen",
"IN_PROGRESS": "In Bearbeitung",
"DONE": "Fertig",
"ADD_CARD": "Karte hinzufügen",
"DELETE_CARD": "Karte löschen",
"PRIORITY": "Priorität",
"ASSIGNEE": "Zugewiesen an"
}Locale Data
Configure Locale Settings
import { Component } from '@angular/core';
import { L10n } from '@syncfusion/ej2-base';
// Set locale for Kanban component
L10n.load({
'ar': {
'kanban': {
'addCard': 'إضافة بطاقة',
'deleteCard': 'حذف البطاقة',
'editCard': 'تعديل البطاقة',
'openDialog': 'فتح الحوار'
}
},
'fr': {
'kanban': {
'addCard': 'Ajouter une carte',
'deleteCard': 'Supprimer la carte',
'editCard': 'Modifier la carte',
'openDialog': 'Ouvrir la boîte de dialogue'
}
}
});
@Component({
selector: 'app-kanban-locale',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
locale="ar"
[dataSource]="data">
</ejs-kanban>
`
})
export class KanbanLocaleComponent {
data = [{ Id: 1, Status: 'Open', Summary: 'Task 1' }];
}Supported Locales
- Arabic (ar)
- Chinese (zh)
- Dutch (nl)
- English (en)
- French (fr)
- German (de)
- Italian (it)
- Japanese (ja)
- Korean (ko)
- Portuguese (pt)
- Russian (ru)
- Spanish (es)
- Swedish (sv)
- Turkish (tr)
Accessibility Checklist
- [ ]
allowKeyboardenabled - [ ] ARIA labels added to regions
- [ ] Focus indicators visible
- [ ] Semantic HTML used
- [ ] Color not sole indicator
- [ ] Card content readable
- [ ] Screen reader tested
- [ ] Keyboard navigation works
- [ ] Contrast ratio WCAG AA (4.5:1)
- [ ] Font size minimum 12px
Testing Accessibility
Automated Testing
npm install --save-dev @axe-core/cli
npx axe https://localhost:4200Manual Testing
1. Keyboard Only - Navigate using only Tab and Arrow keys 2. Screen Reader - Test with NVDA (Windows) or VoiceOver (Mac) 3. Color Contrast - Use WebAIM Contrast Checker 4. WCAG Validator - Use WAVE browser extension
Best Practices
1. Always provide descriptive labels 2. Use semantic HTML (article, section, heading roles) 3. Ensure color contrast ratios meet WCAG AA 4. Test with real assistive technology 5. Support keyboard-only navigation 6. Provide text alternatives for visual content 7. Use aria-live for dynamic updates 8. Test with actual users who use assistive tech
Cards and Data Binding in Angular Kanban
Table of Contents
- Card Structure
- Card Fields Configuration
- Local Data Binding
- Remote Data Binding
- DataManager Integration
- Card Templates
- Card Selection
- OData Support
Card Structure
Each Kanban card displays task information with:
- Header - Usually the task ID
- Content - Task description/summary
- Grabber - Colored indicator on left
- Custom fields - Tags, priority, assignee, etc.
┌──────────────────────────┐
│ [Purple▌] ID-123 │ ← Grabber + Header
├──────────────────────────┤
│ Implement authentication │ ← Content
│ │
│ Tags: Auth, Security │ ← Custom fields
└──────────────────────────┘Card Fields Configuration
Define which data fields appear in cards using cardSettings:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[cardSettings]="cardSettings">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
cardSettings: CardSettingsModel = {
headerField: 'Id', // Card header
contentField: 'Summary', // Card content
grabberField: 'Priority', // Color indicator
tagsField: 'Tags', // Tags display
footerCssField: 'StatusIcon', // Footer styling
selectionType: 'Single', // Single or Multiple
showHeader: true // Show/hide header
};
data = [
{
Id: 'TASK-1',
Status: 'Open',
Summary: 'Design homepage mockup',
Priority: 'High',
Tags: 'Design,UI',
StatusIcon: 'e-card-critical'
}
];
}CardSettingsModel Properties
| Property | Type | Purpose |
|---|---|---|
headerField | string | Data field for card header (usually ID) |
contentField | string | Data field for card content (usually Summary) |
tagsField | string | Data field for tags display |
grabberField | string | Data field for color indicator |
footerCssField | string | CSS class for card footer |
template | string \ | Function |
selectionType | 'Single' \ | 'Multiple' |
showHeader | boolean | Show/hide card header |
Local Data Binding
Bind an array of objects directly to the Kanban:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="tasks"
[cardSettings]="{headerField: 'Id', contentField: 'Summary'}">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
tasks = [
{ Id: 1, Status: 'Open', Summary: 'Design UI mockup' },
{ Id: 2, Status: 'Open', Summary: 'Setup database' },
{ Id: 3, Status: 'InProgress', Summary: 'API development' },
{ Id: 4, Status: 'InProgress', Summary: 'Frontend components' },
{ Id: 5, Status: 'Close', Summary: 'Testing complete' },
{ Id: 6, Status: 'Close', Summary: 'Deployment done' }
];
}When to use:
- Small datasets (<100 items)
- In-memory data or local cache
- Quick prototyping
Remote Data Binding
Fetch data from a server and update Kanban dynamically:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { HttpClientModule } from '@angular/common/http';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule, HttpClientModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="remoteData"
[cardSettings]="{headerField: 'Id', contentField: 'Summary'}">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent implements OnInit {
remoteData: any = [];
constructor(private http: HttpClient) {}
ngOnInit(): void {
this.loadKanbanData();
}
loadKanbanData(): void {
this.http.get('/api/tasks').subscribe((data: any) => {
this.remoteData = data;
});
}
}API endpoint response format:
[
{ "Id": 1, "Status": "Open", "Summary": "Task 1" },
{ "Id": 2, "Status": "InProgress", "Summary": "Task 2" }
]DataManager Integration
Use DataManager for advanced data operations (filtering, sorting, paging):
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="dataManager"
[cardSettings]="{headerField: 'Id', contentField: 'Summary'}">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
dataManager: DataManager = new DataManager({
url: 'https://api.example.com/tasks',
adaptor: new UrlAdaptor()
});
}DataManager with Local Data
import { DataManager } from '@syncfusion/ej2-data';
dataManager: DataManager = new DataManager({
json: [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
]
});Card Templates
Create custom card layouts:
Basic Template with ng-template (RECOMMENDED)
This is the correct and recommended approach for Angular Kanban card templates.
import { Component } from '@angular/core';
import { KanbanModule, CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[cardSettings]="cardSettings">
<ng-template #cardSettingsTemplate let-data>
<div class="e-card-content">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<strong>{{ data.Id }}</strong>
<span [style.backgroundColor]="getPriorityColor(data.Priority)"
style="padding: 2px 8px; color: white; border-radius: 4px; font-size: 12px;">
{{ data.Priority }}
</span>
</div>
<p>{{ data.Summary }}</p>
<div style="margin-top: 8px; font-size: 12px; color: #666;">
<strong>Assignee:</strong> {{ data.Assignee }}
</div>
</div>
</ng-template>
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1', Priority: 'High', Assignee: 'Nancy' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2', Priority: 'Low', Assignee: 'Janet' }
];
// IMPORTANT: Reference the ng-template in cardSettings
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary',
template: '#cardSettingsTemplate' // Must reference the ng-template
};
getPriorityColor(priority: string): string {
const colors: { [key: string]: string } = {
'High': '#d32f2f',
'Medium': '#f57c00',
'Low': '#388e3c'
};
return colors[priority] || '#333';
}
}Key Points: 1. Use <ng-template #cardSettingsTemplate let-data> inside <ejs-kanban> 2. Reference it in cardSettings: template: '#cardSettingsTemplate' 3. Use Angular interpolation: {{ data.Field }} 4. Component methods are accessible: getPriorityColor(data.Priority) 5. Use let-data (NOT let-data="data") to bind the card data context
Template with String Literal (ALTERNATIVE - Less Recommended)
Use this approach only when ng-template is not suitable. String templates are harder to maintain.
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary',
template: `
<div class="e-card-custom">
<div class="e-card-header">
<span class="e-card-header-title" style="font-weight: bold;">${ data.Id }</span>
<span class="e-card-header-icon">🏷️</span>
</div>
<div class="e-card-content">
<p>${ data.Summary }</p>
<div class="e-card-footer">
<span>${ data.Tags }</span>
</div>
</div>
</div>
`
};Card Selection
Configure how users select cards:
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary',
selectionType: 'Multiple' // Allow selecting multiple cards
};Get Selected Cards
import { Component, ViewChild } from '@angular/core';
import { KanbanComponent } from '@syncfusion/ej2-angular-kanban';
export class MyKanbanComponent {
@ViewChild('kanban') kanban: KanbanComponent;
getSelectedCards(): void {
const selectedCards = this.kanban.getSelectedCards();
console.log('Selected:', selectedCards);
}
}OData Support
Bind Kanban to OData v4 services:
import { Component } from '@angular/core';
import { DataManager, ODataV4Adaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-kanban',
standalone: true,
template: `<ejs-kanban [dataSource]="dataManager" keyField="Status"></ejs-kanban>`
})
export class KanbanComponent {
dataManager: DataManager = new DataManager({
url: 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders',
adaptor: new ODataV4Adaptor(),
pageSettings: { pageSize: 50 }
});
}OData URL format:
https://odata-service/api/tasks?$select=Id,Status,Summary&$filter=Status eq 'Open'Data Updates
Adding Cards
@ViewChild('kanban') kanban: KanbanComponent;
addCard(): void {
this.kanban.addCard({
Id: 7,
Status: 'Open',
Summary: 'New task'
});
}Updating Cards
updateCard(): void {
this.kanban.updateCard({
Id: 1,
Status: 'InProgress',
Summary: 'Updated task'
});
}Deleting Cards
deleteCard(): void {
this.kanban.deleteCard(1); // Delete by ID
}Columns and Swimlanes in Angular Kanban
Table of Contents
- Column Configuration
- Single-Key Mapping
- Multi-Key Mapping
- Column Headers
- Stacked Headers
- Swimlanes
- Dynamic Columns
- Column Templates
Column Configuration
Kanban columns represent workflow stages. Define columns using the <e-columns> element:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { ColumnsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Testing" keyField="Testing"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
];
}Column properties:
headerText- Column title displayed to userskeyField- Maps to data field value (must match Status values in data)template- Custom HTML template for headershowItemCount- Display card count in headerallowToggle- Enable column collapse/expandisExpanded- Default expanded stateminCount- Minimum cards allowed (WIP limit)maxCount- Maximum cards allowed (WIP limit)
Single-Key Mapping
Each data object has a Status field that maps to a single column:
// Data structure
{ Id: 1, Status: 'Open', Summary: 'Design UI' }
{ Id: 2, Status: 'Open', Summary: 'Setup project' }
{ Id: 3, Status: 'InProgress', Summary: 'Implement features' }
{ Id: 4, Status: 'Close', Summary: 'Deploy app' }
// Column mapping with keyField
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>How it works: 1. Kanban reads the keyField="Status" property 2. For each data item, it looks at the Status value 3. It places the item in the column with matching keyField 4. Example: Status='Open' → goes to column with keyField="Open"
Multi-Key Mapping
A single column can accept multiple key values:
// Column accepts both "Open" and "New" status values
<e-column headerText="Todo" keyField={['Open', 'New']}></e-column>
// Template syntax in Angular
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column headerText="To Do" [keyField]="['Open', 'New']"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>Use cases:
- Combine similar statuses (Open, Pending, New → "To Do")
- Group related stages (Testing, QA, Review → "Review")
- Legacy data with varied status values
Column Headers
Basic Header
<e-column headerText="In Progress" keyField="InProgress"></e-column>Header with Item Count
<e-column
headerText="In Progress"
keyField="InProgress"
showItemCount="true">
</e-column>Shows: "In Progress (3)" where 3 is the card count in that column.
Custom Header Template
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column keyField="Open">
<ng-template #headerTemplate>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 20px;">📋</span>
<span>To Do</span>
</div>
</ng-template>
</e-column>
<e-column keyField="InProgress">
<ng-template #headerTemplate>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 20px;">⚙️</span>
<span>In Progress</span>
</div>
</ng-template>
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [{ Id: 1, Status: 'Open', Summary: 'Task 1' }];
}Stacked Headers
Group related columns under a common parent header:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { StackedHeadersModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[stackedHeaders]="stackedHeaders">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Testing" keyField="Testing"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
stackedHeaders: StackedHeadersModel[] = [
{
text: 'In Development',
keyFields: 'InProgress' // Comma-separated string of column keyFields
},
{
text: 'Verification',
keyFields: 'Testing'
},
{
text: 'Completed',
keyFields: 'Close'
}
];
data = [{ Id: 1, Status: 'Open', Summary: 'Task 1' }];
}This displays parent headers grouping related workflow stages.
Swimlanes
Swimlanes group cards by a specific field (e.g., Assignee, Priority, Epic):
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { SwimlaneSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[swimlaneSettings]="swimlaneSettings">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
swimlaneSettings: SwimlaneSettingsModel = {
keyField: 'Assignee'
};
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1', Assignee: 'Nancy' },
{ Id: 2, Status: 'Open', Summary: 'Task 2', Assignee: 'Janet' },
{ Id: 3, Status: 'InProgress', Summary: 'Task 3', Assignee: 'Nancy' },
{ Id: 4, Status: 'Close', Summary: 'Task 4', Assignee: 'Janet' }
];
}Result: Cards are grouped horizontally by Assignee:
Nancy ┌─────────────────────────────────────────┐
│ [To Do] [In Progress] [Done] │
│ Task1 Task3 │
└─────────────────────────────────────────┘
Janet ┌─────────────────────────────────────────┐
│ [To Do] [In Progress] [Done] │
│ Task2 Task4 │
└─────────────────────────────────────────┘Swimlane with Custom Template
swimlaneSettings: SwimlaneSettingsModel = {
keyField: 'Assignee',
template: '<div style="padding: 10px; background: #f5f5f5;">{{ data.Assignee }} - Team Member</div>'
};Dynamic Columns
Add and remove columns programmatically:
import { Component, ViewChild } from '@angular/core';
import { KanbanModule, KanbanComponent } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<button (click)="addNewColumn()">Add Column</button>
<button (click)="removeColumn()">Remove Column</button>
<ejs-kanban
#kanbanRef
keyField="Status"
[dataSource]="data">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
@ViewChild('kanbanRef') kanban: KanbanComponent;
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
];
addNewColumn(): void {
this.kanban.addColumn(
{ headerText: 'Testing', keyField: 'Testing' },
2
);
}
removeColumn(): void {
this.kanban.removeColumn(0);
}
}Show/Hide Columns
// Hide a column by key
hideColumn(key: string): void {
this.kanban.hideColumn(key);
}
// Show a hidden column
showColumn(key: string): void {
this.kanban.showColumn(key);
}Column Templates
Custom rendering for column headers and content areas:
<ejs-kanban keyField="Status">
<e-columns>
<e-column keyField="Open">
<ng-template #headerTemplate>
<div style="color: red; font-weight: bold;">🔴 Urgent</div>
</ng-template>
</e-column>
</e-columns>
</ejs-kanban>Common patterns:
- Icons with text labels
- Status indicators with colors
- Card count badges
- Collapsible column headers
- Column action buttons
Data Persistence in Angular Kanban
Table of Contents
- Local Storage Persistence
- State Save and Restore
- DataManager Persistence
- Custom Persistence
- Backend Sync
Local Storage Persistence
Save Board State to Browser Storage
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { KanbanComponent, KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { ActionEventArgs } from '@syncfusion/ej2-kanban';
@Component({
selector: 'app-kanban-persistent',
standalone: true,
imports: [KanbanModule],
template: `
<button (click)="clearSavedState()">Clear Saved State</button>
<ejs-kanban
#kanban
keyField="Status"
[dataSource]="data"
(actionComplete)="onActionComplete($event)">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanPersistentComponent implements OnInit, OnDestroy {
@ViewChild('kanban') kanban: KanbanComponent;
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
];
ngOnInit(): void {
// Restore board state from storage
this.restoreState();
}
onActionComplete(args: ActionEventArgs): void {
// Save state after any action
if (['Drag', 'Save', 'Delete', 'Add'].includes(args.requestType)) {
this.saveState();
}
}
saveState(): void {
const boardState = {
timestamp: new Date().getTime(),
data: this.data,
cardPositions: this.getCardPositions()
};
localStorage.setItem('kanban-state', JSON.stringify(boardState));
console.log('State saved to localStorage');
}
restoreState(): void {
const savedState = localStorage.getItem('kanban-state');
if (savedState) {
try {
const state = JSON.parse(savedState);
this.data = state.data;
console.log('State restored from localStorage');
} catch (error) {
console.error('Error restoring state:', error);
}
}
}
clearSavedState(): void {
localStorage.removeItem('kanban-state');
console.log('Saved state cleared');
}
private getCardPositions(): any {
// Get current card positions in columns
return this.data.reduce((positions, card) => {
if (!positions[card.Status]) {
positions[card.Status] = [];
}
positions[card.Status].push(card.Id);
return positions;
}, {});
}
ngOnDestroy(): void {
// Auto-save on component destroy
this.saveState();
}
}Session vs Local Storage
// Local Storage - Persists across browser sessions
localStorage.setItem('kanban-data', JSON.stringify(data));
// Session Storage - Cleared when tab closes
sessionStorage.setItem('kanban-data', JSON.stringify(data));
// Choose based on use case:
// - Local: Project management (permanent saves)
// - Session: Temporary workflow (single session work)State Save and Restore
Save Card Order and Positions
export interface KanbanState {
version: string;
savedAt: Date;
cards: CardState[];
columnStates: ColumnState[];
}
export interface CardState {
id: string | number;
status: string;
order: number;
}
export interface ColumnState {
key: string;
isExpanded: boolean;
isVisible: boolean;
}
saveBoardConfiguration(): void {
const state: KanbanState = {
version: '1.0.0',
savedAt: new Date(),
cards: this.data.map((card, index) => ({
id: card.Id,
status: card.Status,
order: index
})),
columnStates: this.kanban.getColumnStates?.() || []
};
localStorage.setItem('kanban-config', JSON.stringify(state));
}
restoreBoardConfiguration(): void {
const saved = localStorage.getItem('kanban-config');
if (saved) {
const state: KanbanState = JSON.parse(saved);
// Restore card order
this.data = this.data.sort((a, b) => {
const aCard = state.cards.find(c => c.id === a.Id);
const bCard = state.cards.find(c => c.id === b.Id);
return (aCard?.order || 0) - (bCard?.order || 0);
});
}
}Card Movement History
class KanbanHistoryManager {
private history: CardMovement[] = [];
private historyIndex = -1;
recordMovement(card: any, oldStatus: string, newStatus: string): void {
// Remove redo history when new move is made
this.history.splice(this.historyIndex + 1);
this.history.push({
cardId: card.Id,
oldStatus,
newStatus,
timestamp: new Date()
});
this.historyIndex++;
}
undo(): CardMovement | null {
if (this.historyIndex > 0) {
this.historyIndex--;
return this.history[this.historyIndex + 1];
}
return null;
}
redo(): CardMovement | null {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
return this.history[this.historyIndex];
}
return null;
}
canUndo(): boolean {
return this.historyIndex >= 0;
}
canRedo(): boolean {
return this.historyIndex < this.history.length - 1;
}
}
interface CardMovement {
cardId: string | number;
oldStatus: string;
newStatus: string;
timestamp: Date;
}DataManager Persistence
Auto-Save with DataManager
import { Component } from '@angular/core';
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
import { ActionEventArgs } from '@syncfusion/ej2-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<ejs-kanban
keyField="Status"
[dataSource]="dataManager"
(actionComplete)="onActionComplete($event)">
</ejs-kanban>
`
})
export class KanbanComponent {
dataManager: DataManager = new DataManager({
url: '/api/tasks',
adaptor: new UrlAdaptor(),
actionFailure: () => {
console.error('API error - data not saved');
}
});
onActionComplete(args: ActionEventArgs): void {
if (args.requestType === 'Save') {
console.log('Card saved to backend:', args.data);
}
}
}Batch Updates
saveBatchChanges(): void {
const dataManager = this.kanban.getDataManager();
// Make changes
dataManager.insert(newCard);
dataManager.update('Id', updatedCard);
dataManager.remove('Id', deletedCard);
// Save all at once
dataManager.saveChanges().then(() => {
console.log('Batch changes saved');
}).catch((error) => {
console.error('Batch save failed:', error);
});
}Custom Persistence
IndexedDB for Large Datasets
class KanbanIndexedDBStorage {
private dbName = 'kanban-db';
private storeName = 'cards';
private db: IDBDatabase;
async init(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event: any) => {
const db = event.target.result;
db.createObjectStore(this.storeName, { keyPath: 'Id' });
};
});
}
async saveCards(cards: any[]): Promise<void> {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
for (const card of cards) {
store.put(card);
}
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(transaction.error);
});
}
async loadCards(): Promise<any[]> {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
// Usage
const storage = new KanbanIndexedDBStorage();
await storage.init();
await storage.saveCards(kanbanData);
const loadedCards = await storage.loadCards();Backend Sync
Sync Local Changes to Server
class KanbanSyncManager {
private syncQueue: SyncOperation[] = [];
private isSyncing = false;
async addOperation(operation: SyncOperation): Promise<void> {
this.syncQueue.push(operation);
await this.sync();
}
async sync(): Promise<void> {
if (this.isSyncing || this.syncQueue.length === 0) {
return;
}
this.isSyncing = true;
const operations = [...this.syncQueue];
this.syncQueue = [];
try {
for (const op of operations) {
switch (op.type) {
case 'ADD':
await this.apiAddCard(op.data);
break;
case 'UPDATE':
await this.apiUpdateCard(op.data);
break;
case 'DELETE':
await this.apiDeleteCard(op.data.Id);
break;
}
}
console.log('Sync completed');
} catch (error) {
console.error('Sync failed:', error);
// Re-add failed operations to queue
this.syncQueue.push(...operations);
} finally {
this.isSyncing = false;
}
}
private apiAddCard(card: any): Promise<any> {
return fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(card)
}).then(r => r.json());
}
private apiUpdateCard(card: any): Promise<any> {
return fetch(`/api/tasks/${card.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(card)
}).then(r => r.json());
}
private apiDeleteCard(id: string | number): Promise<any> {
return fetch(`/api/tasks/${id}`, { method: 'DELETE' })
.then(r => r.json());
}
}
interface SyncOperation {
type: 'ADD' | 'UPDATE' | 'DELETE';
data: any;
timestamp: Date;
}Offline-First Pattern
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<div *ngIf="!isOnline" style="background: #fff3cd; padding: 10px; color: #856404;">
⚠️ Offline Mode - Changes will sync when connected
</div>
<ejs-kanban
(actionComplete)="onActionComplete($event)">
</ejs-kanban>
`
})
export class KanbanOfflineComponent {
isOnline = navigator.onLine;
private syncManager = new KanbanSyncManager();
constructor() {
window.addEventListener('online', () => this.onOnline());
window.addEventListener('offline', () => this.onOffline());
}
onActionComplete(args: ActionEventArgs): void {
// Queue operation for sync
this.syncManager.addOperation({
type: args.requestType as 'ADD' | 'UPDATE' | 'DELETE',
data: args.data,
timestamp: new Date()
});
}
private onOnline(): void {
this.isOnline = true;
console.log('Back online - syncing...');
this.syncManager.sync();
}
private onOffline(): void {
this.isOnline = false;
console.log('Offline - changes will be saved locally');
}
}Best Practices
1. Auto-save after drag - Save immediately after card moves 2. Debounce saves - Wait 500ms before saving to avoid rapid requests 3. Conflict resolution - Handle cases where server data differs from local 4. Version control - Track state versions for migration 5. Error handling - Gracefully handle sync failures 6. User notification - Show save status to users
Drag and Drop in Angular Kanban
Table of Contents
- Overview
- Enabling Drag and Drop
- Column to Column Movement
- Within Column Reordering
- Drag Events
- WIP Limits
- Transition Columns
- Preventing Drag
Overview
Drag-and-drop is the core interaction in Kanban. It allows users to:
- Move cards between columns (change status)
- Reorder cards within a column
- Organize tasks based on workflow progress
Drag-and-drop is enabled by default via the allowDragAndDrop property.
Enabling Drag and Drop
Default Behavior (Enabled)
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[allowDragAndDrop]="true">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
];
}Disable Drag and Drop
<ejs-kanban
[allowDragAndDrop]="false"
keyField="Status">
</ejs-kanban>When disabled, cards cannot be moved by users (but can be updated via API).
Column to Column Movement
Move cards between columns to change their status:
// Visual representation
┌──────────────┐ ┌─────────────┐ ┌────────────┐
│ To Do │ │ In Progress │ │ Done │
├──────────────┤ ├─────────────┤ ├────────────┤
│ Task 1 ──────┼───→│ Task 1 │ │ │
│ Task 2 │ │ Task 3 ─────┼───→│ Task 3 │
│ Task 4 │ │ │ │ Task 5 │
└──────────────┘ └─────────────┘ └────────────┘When a card moves: 1. User drags card from "To Do" to "In Progress" 2. Kanban updates the Status field to "InProgress" 3. Card is removed from "To Do" column 4. Card appears in "In Progress" column 5. dragStop event fires with updated data
Example Data Update
// Before drag
{ Id: 1, Status: 'Open', Summary: 'Task 1' }
// After dragging to In Progress column
{ Id: 1, Status: 'InProgress', Summary: 'Task 1' }Within Column Reordering
Reorder cards within the same column (change card order):
// Drag card within To Do column
┌──────────────┐
│ To Do │
├──────────────┤
│ Task 2 ↑ │
│ Task 1 ↓ (drag Task 1 up)
│ Task 4 │
│ Task 3 │
└──────────────┘
// After reordering
┌──────────────┐
│ To Do │
├──────────────┤
│ Task 1 │ (moved up)
│ Task 2 │ (moved down)
│ Task 4 │
│ Task 3 │
└──────────────┘The RankId field (if present) may be updated to reflect new order.
Drag Events
Listen to drag lifecycle events:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { DragEventArgs } from '@syncfusion/ej2-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
(beforeDragStart)="onBeforeDragStart($event)"
(dragStart)="onDragStart($event)"
(dragStop)="onDragStop($event)">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
];
onBeforeDragStart(args: DragEventArgs): void {
console.log('Drag starting:', args.data);
// Can cancel drag by setting args.cancel = true
}
onDragStart(args: DragEventArgs): void {
console.log('Dragging card:', args.data);
// Can show visual feedback here
}
onDragStop(args: DragEventArgs): void {
console.log('Drag completed:', args.data);
console.log('New status:', args.data.Status);
// Save to backend here
}
}DragEventArgs Properties
| Property | Type | Purpose |
|---|---|---|
data | Object | Card being dragged |
element | HTMLElement | Card DOM element |
event | MouseEvent | Mouse event details |
cancel | boolean | Set to true to cancel drag |
target | HTMLElement | Drop target element |
clonedElement | HTMLElement | Visual clone during drag |
WIP Limits
Enforce Work-In-Progress limits to restrict card counts per column:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { ColumnsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column
headerText="To Do"
keyField="Open"
minCount="1"
maxCount="5">
</e-column>
<e-column
headerText="In Progress"
keyField="InProgress"
minCount="0"
maxCount="3">
</e-column>
<e-column
headerText="Done"
keyField="Close"
minCount="0"
maxCount="10">
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'Open', Summary: 'Task 2' }
];
}Limit Behavior
minCount=0, maxCount=3 means:
- Column can have 0-3 cards
- If column has 3 cards, new cards cannot be dropped (user gets error)
- If column has 0 cards, warning indicator appears
- Helps prevent bottlenecks in workflow
Example validation:
┌─────────────────┐
│ In Progress │
│ (3/3 max) │ ← Full! Cannot add more
├─────────────────┤
│ [Task 1] │
│ [Task 2] │
│ [Task 3] │
└─────────────────┘WIP with Swimlanes
Enforce WIP per swimlane within each column:
swimlaneSettings: {
keyField: 'Assignee'
}
columns: [
{
headerText: 'In Progress',
keyField: 'InProgress',
minCount: 0,
maxCount: 2 // Each person: max 2 tasks in progress
}
]Transition Columns
Restrict which columns can accept cards from other columns using the transitionColumns property. This enforces workflow rules and prevents invalid state transitions.
Basic Transition Control
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { ColumnsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data">
<e-columns>
<e-column
headerText="To Do"
keyField="Open"
[transitionColumns]="['InProgress']">
</e-column>
<e-column
headerText="In Progress"
keyField="InProgress"
[transitionColumns]="['Testing', 'Close']">
</e-column>
<e-column
headerText="Testing"
keyField="Testing"
[transitionColumns]="['Close', 'Open']">
</e-column>
<e-column
headerText="Done"
keyField="Close">
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1' },
{ Id: 2, Status: 'InProgress', Summary: 'Task 2' }
];
}Effect:
- "To Do" cards can only move to "In Progress"
- "In Progress" cards can move to "Testing" or "Done"
- "Testing" cards can move to "Done" or back to "To Do" (if bugs found)
- "Done" cards cannot be moved (no transitionColumns specified)
Linear Workflow (No Skipping Steps)
Enforce a strict sequential workflow where each step must be completed in order:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column
headerText="Analysis"
keyField="Analysis"
[transitionColumns]="['Design']">
</e-column>
<e-column
headerText="Design"
keyField="Design"
[transitionColumns]="['Development']">
</e-column>
<e-column
headerText="Development"
keyField="Development"
[transitionColumns]="['Testing']">
</e-column>
<e-column
headerText="Testing"
keyField="Testing"
[transitionColumns]="['Deployed']">
</e-column>
<e-column
headerText="Deployed"
keyField="Deployed"
[allowDrag]="false">
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Analysis', Summary: 'Feature A' },
{ Id: 2, Status: 'Development', Summary: 'Feature B' }
];
}Effect:
- Cards must progress through: Analysis → Design → Development → Testing → Deployed
- No skipping steps allowed
- No moving backwards
- Final "Deployed" column is locked (cannot drag out)
Approval Flow with Multiple Paths
Allow different paths for different approval levels:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban keyField="Status" [dataSource]="data">
<e-columns>
<e-column
headerText="Submitted"
keyField="Open"
[transitionColumns]="['Review']">
</e-column>
<e-column
headerText="Under Review"
keyField="Review"
[transitionColumns]="['Approved', 'Rejected', 'Open']">
</e-column>
<e-column
headerText="Approved"
keyField="Approved"
[transitionColumns]="['Deployed']">
</e-column>
<e-column
headerText="Rejected"
keyField="Rejected"
[transitionColumns]="['Open']">
</e-column>
<e-column
headerText="Deployed"
keyField="Deployed"
[allowDrag]="false">
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [
{ Id: 1, Status: 'Open', Summary: 'Change Request 1' },
{ Id: 2, Status: 'Review', Summary: 'Change Request 2' }
];
}Per-Column Drag and Drop Control
Combine transitionColumns with allowDrag and allowDrop:
<e-columns>
<e-column
headerText="Archive"
keyField="Archive"
[allowDrag]="true" // Can restore from archive
[allowDrop]="true" // Can archive items
[transitionColumns]="['Open']"> // Can only restore to Open
</e-column>
<e-column
headerText="Open"
keyField="Open"
[allowDrag]="true"
[allowDrop]="true"
[transitionColumns]="['InProgress', 'Archive']">
</e-column>
<e-column
headerText="Locked"
keyField="Locked"
[allowDrag]="false" // Cannot move once locked
[allowDrop]="true"
[transitionColumns]="[]"> // No outbound transitions
</e-column>
</e-columns>Effect:
- Archive column allows restoring items back to Open
- Open items can move to InProgress or be archived
- Locked column is a one-way trap (items cannot be moved out)
Preventing Drag
Cancel Drag in Event
onBeforeDragStart(args: DragEventArgs): void {
// Don't allow dragging cards with Critical priority
if (args.data.Priority === 'Critical') {
args.cancel = true;
alert('Cannot move critical tasks');
}
}Disable for Specific Cards
// Add a read-only flag to data
{ Id: 1, Status: 'Open', Summary: 'Task 1', readOnly: true }
// Check in event
onBeforeDragStart(args: DragEventArgs): void {
if (args.data.readOnly) {
args.cancel = true;
}
}Keyboard Shortcuts During Drag
- Arrow Keys - Navigate to adjacent cards
- Escape - Cancel drag operation
- Enter - Confirm drop (if using custom drop zones)
Advanced Drag Scenarios
Multi-Select and Drag
When multiple cards are selected, dragging one moves all:
cardSettings: {
selectionType: 'Multiple'
}
// Select multiple cards (Ctrl+Click or Shift+Click)
// Then drag one → all selected cards moveDrag to External Target
Listen to drag events to handle drops outside Kanban:
onDragStop(args: DragEventArgs): void {
if (args.target && args.target.id === 'external-zone') {
// Handle external drop
console.log('Dropped outside Kanban:', args.data);
}
}Drag Validation
onDragStop(args: DragEventArgs): void {
// Example: Prevent moving to Done without approval
if (args.data.Status === 'Close' && !args.data.ApprovedBy) {
alert('Card must be approved before closing');
// Need to revert status (reload data)
}
}Performance Tips for Drag and Drop
- Virtual scrolling - For 1000+ cards, enable virtual scrolling
- Minimize event handlers - Keep onDragStop logic fast
- Batch updates - Group multiple drag operations
- Debounce saves - Wait 500ms after drag before saving to backend
Events Reference for Angular Kanban
Table of Contents
- Action Events
- Card Events
- Drag Events
- Dialog Events
- Selection Events
- Column and Swimlane Events
- Complete Events Example
Action Events
actionBegin: EmitType<ActionEventArgs>
Fires at the beginning of every Kanban action.
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { ActionEventArgs } from '@syncfusion/ej2-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
(actionBegin)="onActionBegin($event)">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
data = [{ Id: 1, Status: 'Open', Summary: 'Task 1' }];
onActionBegin(args: ActionEventArgs): void {
console.log('Action starting:', args.requestType);
// requestType: 'Save', 'Delete', 'Drag', 'Add', etc.
// Example: Prevent deletion
if (args.requestType === 'Delete') {
if (confirm('Are you sure?')) {
args.cancel = false;
} else {
args.cancel = true;
}
}
}
}ActionEventArgs Properties
| Property | Type | Purpose |
|---|---|---|
requestType | string | Action type ('Add', 'Delete', 'Save', 'Drag', etc.) |
data | Object \ | Object[] |
cancel | boolean | Set to true to cancel action |
addedRecords | Object[] | Cards being added |
changedRecords | Object[] | Cards being modified |
deletedRecords | Object[] | Cards being deleted |
columnData | Object[] | Column information |
cardData | Object | Current card object |
actionComplete: EmitType<ActionEventArgs>
Fires after action completes successfully.
onActionComplete(args: ActionEventArgs): void {
console.log('Action completed:', args.requestType);
if (args.requestType === 'Save') {
console.log('Card saved:', args.data);
// Refresh UI or show notification
}
}actionFailure: EmitType<ActionEventArgs>
Fires when an action fails.
onActionFailure(args: ActionEventArgs): void {
console.error('Action failed:', args.requestType);
console.error('Error:', args.error);
// Show error message to user
}Card Events
cardClick: EmitType<CardClickEventArgs>
Fires when a card is clicked.
import { CardClickEventArgs } from '@syncfusion/ej2-kanban';
(cardClick)="onCardClick($event)"
onCardClick(args: CardClickEventArgs): void {
console.log('Card clicked:', args.data);
console.log('Card element:', args.element);
// Example: Show card details
this.showCardDetails(args.data);
}cardDoubleClick: EmitType<CardClickEventArgs>
Fires when a card is double-clicked.
(cardDoubleClick)="onCardDoubleClick($event)"
onCardDoubleClick(args: CardClickEventArgs): void {
console.log('Card double-clicked:', args.data);
// Open edit dialog
this.kanban.openDialog(args.data, 'Edit');
}CardClickEventArgs Properties
| Property | Type | Purpose |
|---|---|---|
data | Object | Card data object |
element | HTMLElement | Card DOM element |
event | MouseEvent | Mouse event details |
cancel | boolean | Set to true to cancel |
Drag Events
dragStart: EmitType<DragEventArgs>
Fires when card drag starts.
import { DragEventArgs } from '@syncfusion/ej2-kanban';
(dragStart)="onDragStart($event)"
onDragStart(args: DragEventArgs): void {
console.log('Dragging card:', args.data);
// Show visual feedback
args.clonedElement.style.opacity = '0.7';
}dragStop: EmitType<DragEventArgs>
Fires when card drag completes.
(dragStop)="onDragStop($event)"
onDragStop(args: DragEventArgs): void {
console.log('Drag complete');
console.log('Old Status:', args.data.Status);
console.log('New Status:', args.data.Status); // Updated
// Save to backend
this.saveCardStatus(args.data);
}beforeDragStart: EmitType<DragEventArgs>
Fires before drag starts (for validation).
(beforeDragStart)="onBeforeDragStart($event)"
onBeforeDragStart(args: DragEventArgs): void {
// Prevent dragging cards with critical priority
if (args.data.Priority === 'Critical') {
args.cancel = true;
alert('Cannot move critical items');
}
}DragEventArgs Properties
| Property | Type | Purpose |
|---|---|---|
data | Object | Card being dragged |
element | HTMLElement | Card DOM element |
clonedElement | HTMLElement | Visual clone during drag |
event | MouseEvent | Mouse event |
target | HTMLElement | Drop target |
cancel | boolean | Set to true to cancel |
Dialog Events
dialogOpen: EmitType<DialogEventArgs>
Fires when add/edit dialog opens.
import { DialogEventArgs } from '@syncfusion/ej2-kanban';
(dialogOpen)="onDialogOpen($event)"
onDialogOpen(args: DialogEventArgs): void {
console.log('Dialog action:', args.action); // 'Add' or 'Edit'
console.log('Dialog data:', args.data);
// Set default values for new cards
if (args.action === 'Add') {
args.data.Status = 'Open';
args.data.Priority = 'Medium';
}
}dialogClose: EmitType<DialogEventArgs>
Fires when add/edit dialog closes.
(dialogClose)="onDialogClose($event)"
onDialogClose(args: DialogEventArgs): void {
console.log('Dialog closing');
if (args.action === 'Cancel') {
console.log('User cancelled');
} else if (args.action === 'Save') {
console.log('Card saved:', args.data);
}
}DialogEventArgs Properties
| Property | Type | Purpose |
|---|---|---|
action | string | 'Add', 'Edit', 'Cancel', 'Save' |
data | Object | Card data from form |
cancel | boolean | Set to true to cancel |
Selection Events
cardSelection: EmitType<CardSelectionEventArgs>
Fires when card is selected/deselected.
(cardSelection)="onCardSelection($event)"
onCardSelection(args: CardSelectionEventArgs): void {
console.log('Selection changed');
console.log('Selected:', args.selectedCards);
console.log('Deselected:', args.deselectedCards);
}CardSelectionEventArgs Properties
| Property | Type | Purpose |
|---|---|---|
selectedCards | Object[] | Currently selected cards |
deselectedCards | Object[] | Recently deselected cards |
Column and Swimlane Events
columnClick: EmitType<ColumnClickEventArgs>
Fires when column header is clicked.
(columnClick)="onColumnClick($event)"
onColumnClick(args: ColumnClickEventArgs): void {
console.log('Column clicked:', args.columnData);
}swimlaneClick: EmitType<SwimlaneClickEventArgs>
Fires when swimlane header is clicked.
(swimlaneClick)="onSwimlaneClick($event)"
onSwimlaneClick(args: SwimlaneClickEventArgs): void {
console.log('Swimlane clicked:', args.swimlaneData);
}Complete Events Example
import { Component, ViewChild } from '@angular/core';
import { KanbanComponent, KanbanModule } from '@syncfusion/ej2-angular-kanban';
import {
ActionEventArgs,
CardClickEventArgs,
DragEventArgs,
DialogEventArgs,
CardSelectionEventArgs
} from '@syncfusion/ej2-kanban';
@Component({
selector: 'app-kanban-events',
standalone: true,
imports: [KanbanModule],
template: `
<div style="padding: 20px;">
<h2>Kanban Events Demo</h2>
<p>{{ eventLog }}</p>
</div>
<ejs-kanban
#kanban
keyField="Status"
[dataSource]="data"
(actionBegin)="onActionBegin($event)"
(actionComplete)="onActionComplete($event)"
(actionFailure)="onActionFailure($event)"
(cardClick)="onCardClick($event)"
(cardDoubleClick)="onCardDoubleClick($event)"
(dragStart)="onDragStart($event)"
(dragStop)="onDragStop($event)"
(beforeDragStart)="onBeforeDragStart($event)"
(dialogOpen)="onDialogOpen($event)"
(dialogClose)="onDialogClose($event)"
(cardSelection)="onCardSelection($event)">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanEventsComponent {
@ViewChild('kanban') kanban: KanbanComponent;
eventLog: string = 'Events log...';
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1', Priority: 'High' },
{ Id: 2, Status: 'Open', Summary: 'Task 2', Priority: 'Medium' },
{ Id: 3, Status: 'InProgress', Summary: 'Task 3', Priority: 'Low' }
];
log(message: string): void {
this.eventLog = `${new Date().toLocaleTimeString()}: ${message}`;
console.log(message);
}
onActionBegin(args: ActionEventArgs): void {
this.log(`Action Begin: ${args.requestType}`);
}
onActionComplete(args: ActionEventArgs): void {
this.log(`Action Complete: ${args.requestType}`);
}
onActionFailure(args: ActionEventArgs): void {
this.log(`Action Failure: ${args.requestType}`);
}
onCardClick(args: CardClickEventArgs): void {
this.log(`Card Clicked: ID=${args.data.Id}, Status=${args.data.Status}`);
}
onCardDoubleClick(args: CardClickEventArgs): void {
this.log(`Card Double-Clicked: ID=${args.data.Id}`);
}
onDragStart(args: DragEventArgs): void {
this.log(`Drag Start: Card=${args.data.Id}`);
}
onDragStop(args: DragEventArgs): void {
this.log(`Drag Stop: Card=${args.data.Id}, New Status=${args.data.Status}`);
}
onBeforeDragStart(args: DragEventArgs): void {
this.log(`Before Drag Start: Card=${args.data.Id}`);
}
onDialogOpen(args: DialogEventArgs): void {
this.log(`Dialog Open: Action=${args.action}`);
}
onDialogClose(args: DialogEventArgs): void {
this.log(`Dialog Close: Action=${args.action}`);
}
onCardSelection(args: CardSelectionEventArgs): void {
this.log(`Selection Changed: ${args.selectedCards.length} selected`);
}
}Event Handling Patterns
Pattern 1: Validation on Action Begin
onActionBegin(args: ActionEventArgs): void {
if (args.requestType === 'Delete') {
if (!confirm('Delete this card?')) {
args.cancel = true;
}
}
}Pattern 2: Save After Drag
onDragStop(args: DragEventArgs): void {
// Save card status change to backend
this.api.updateCard(args.data).subscribe(
() => this.log('Card saved'),
(error) => this.log('Save failed')
);
}Pattern 3: Load Card Details on Click
onCardClick(args: CardClickEventArgs): void {
// Load full card details
this.api.getCardDetails(args.data.Id).subscribe(
(fullCard) => {
this.showDetailPanel(fullCard);
}
);
}Pattern 4: Pre-fill Dialog
onDialogOpen(args: DialogEventArgs): void {
if (args.action === 'Add') {
// Set defaults for new cards
args.data.Status = 'Open';
args.data.Priority = 'Medium';
args.data.Assignee = this.currentUser;
}
}Features and Customization in Angular Kanban
Table of Contents
- Sorting
- Filtering
- Search
- Validation and WIP
- Dialog Customization
- Tooltip Customization
- Responsive Design
- Color and Status Indicators
Sorting
Sort cards within columns by field or custom function:
import { Component, ViewChild } from '@angular/core';
import { KanbanComponent, KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<button (click)="sortByPriority()">Sort by Priority</button>
<button (click)="sortByAssignee()">Sort by Assignee</button>
<ejs-kanban #kanban keyField="Status" [dataSource]="data"></ejs-kanban>
`
})
export class KanbanComponent {
@ViewChild('kanban') kanban: KanbanComponent;
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1', Priority: 'High' },
{ Id: 2, Status: 'Open', Summary: 'Task 2', Priority: 'Low' }
];
sortByPriority(): void {
this.kanban.sort('Priority', 'Descending');
}
sortByAssignee(): void {
this.kanban.sort('Assignee', 'Ascending');
}
// Custom sort function
sortByEstimate(): void {
this.kanban.sort((a, b) => {
return b.Estimate - a.Estimate; // Highest estimate first
});
}
}Filtering
Filter cards to show only specific items:
filterByStatus(): void {
// Show only high priority items
this.kanban.filter('Priority', 'High');
}
filterByAssignee(): void {
// Show only Nancy's tasks
this.kanban.filter('Assignee', 'Nancy');
}
// Advanced filtering with predicate
filterCustom(): void {
this.kanban.filter((card) => {
return card.Priority === 'High' && card.Assignee === 'Nancy';
});
}
// Clear filter
clearFilter(): void {
this.kanban.filter('', '');
}Search
Find cards containing specific text:
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule, FormsModule],
template: `
<input
[(ngModel)]="searchText"
placeholder="Search cards..."
(keyup)="performSearch()">
<ejs-kanban #kanban keyField="Status" [dataSource]="data"></ejs-kanban>
`
})
export class KanbanComponent {
@ViewChild('kanban') kanban: KanbanComponent;
searchText: string = '';
data = [
{ Id: 1, Status: 'Open', Summary: 'Design authentication module' },
{ Id: 2, Status: 'Open', Summary: 'Setup database schema' }
];
performSearch(): void {
if (this.searchText) {
this.kanban.search(this.searchText);
} else {
this.kanban.search(''); // Clear search
}
}
}Search behavior:
- Searches all searchable fields (Summary, Tags, etc.)
- Case-insensitive
- Real-time filtering as user types
- Clears with empty string
Validation and WIP
Card Validation Dialog
Validate card data before saving:
import { Component } from '@angular/core';
import { DialogEventArgs } from '@syncfusion/ej2-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<ejs-kanban
(dialogOpen)="onDialogOpen($event)"
(actionBegin)="onActionBegin($event)">
</ejs-kanban>
`
})
export class KanbanComponent {
onDialogOpen(args: DialogEventArgs): void {
// Validate required fields
if (!args.data.Summary || args.data.Summary.trim() === '') {
console.warn('Summary is required');
}
}
onActionBegin(args: any): void {
// Validate before save
if (args.requestType === 'Save') {
if (!this.isValidCard(args.data)) {
args.cancel = true;
alert('Please fill all required fields');
}
}
}
isValidCard(card: any): boolean {
return card.Summary &&
card.Status &&
card.Priority;
}
}WIP Limits (Work In Progress)
Restrict maximum cards per column:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban keyField="Status">
<e-columns>
<e-column
headerText="To Do"
keyField="Open"
minCount="1"
maxCount="5">
</e-column>
<e-column
headerText="In Progress"
keyField="InProgress"
minCount="0"
maxCount="3">
</e-column>
<e-column
headerText="Done"
keyField="Close"
maxCount="10">
</e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {}WIP Violation Handling:
- User cannot drop card if max reached
- Warning indicator if min not met
actionFailureevent fires on violation
Dialog Customization
Custom Dialog Template
import { Component } from '@angular/core';
import { DialogSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<ejs-kanban
keyField="Status"
[dialogSettings]="dialogSettings">
</ejs-kanban>
`
})
export class KanbanComponent {
dialogSettings: DialogSettingsModel = {
template: `
<div style="padding: 15px;">
<div>
<label>Task ID</label>
<input id="Id" value="\${data.Id}" />
</div>
<div style="margin-top: 10px;">
<label>Summary</label>
<textarea id="Summary">\${data.Summary}</textarea>
</div>
<div style="margin-top: 10px;">
<label>Priority</label>
<select id="Priority">
<option>High</option>
<option>Medium</option>
<option>Low</option>
</select>
</div>
</div>
`
};
}Custom Dialog Fields
dialogSettings: DialogSettingsModel = {
fields: [
{ text: 'ID', key: 'Id', type: 'TextBox' },
{ text: 'Summary', key: 'Summary', type: 'TextArea' },
{ text: 'Status', key: 'Status', type: 'Dropdown', dataSource: ['Open', 'InProgress', 'Close'] },
{ text: 'Priority', key: 'Priority', type: 'Dropdown', dataSource: ['High', 'Medium', 'Low'] },
{ text: 'Assignee', key: 'Assignee', type: 'TextBox' }
]
};Tooltip Customization
Tooltip Template
import { Component } from '@angular/core';
import { TooltipSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<ejs-kanban
[tooltipSettings]="tooltipSettings">
</ejs-kanban>
`
})
export class KanbanComponent {
tooltipSettings: TooltipSettingsModel = {
template: `
<div style="padding: 10px; background: white; border-radius: 4px;">
<h4>\${data.Id}</h4>
<p><strong>Summary:</strong> \${data.Summary}</p>
<p><strong>Assignee:</strong> \${data.Assignee}</p>
<p><strong>Priority:</strong> \${data.Priority}</p>
<p><strong>Tags:</strong> \${data.Tags}</p>
</div>
`
};
}HTML Tooltip
tooltipSettings: TooltipSettingsModel = {
template: `
<div class="e-tooltip-content">
<div class="e-card-header" style="font-weight: bold; margin-bottom: 8px;">
\${data.Id} - \${data.Summary}
</div>
<div style="display: grid; gap: 6px; font-size: 12px;">
<div><strong>Assigned to:</strong> \${data.Assignee}</div>
<div><strong>Priority:</strong> <span style="color: \${getPriorityColor(data.Priority)};">\${data.Priority}</span></div>
<div><strong>Estimate:</strong> \${data.Estimate} hrs</div>
</div>
</div>
`
};Responsive Design
Mobile-Optimized Kanban
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
height="100%"
width="100%"
[allowKeyboard]="true">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`,
styles: [`
:host {
display: block;
height: 100vh;
width: 100vw;
}
`]
})
export class KanbanComponent {
data = [];
}Responsive Breakpoints
@HostListener('window:resize', ['$event'])
onResize(): void {
const width = window.innerWidth;
if (width < 768) {
// Mobile: Show fewer columns or scroll
this.kanbanHeight = window.innerHeight;
this.cardHeight = '80px';
} else if (width < 1024) {
// Tablet: Normal layout
this.kanbanHeight = window.innerHeight - 60;
this.cardHeight = '100px';
} else {
// Desktop: Full layout
this.kanbanHeight = window.innerHeight - 60;
this.cardHeight = '120px';
}
}Color and Status Indicators
Priority Color Grabber
import { Component } from '@angular/core';
import { CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<ejs-kanban
keyField="Status"
[cardSettings]="cardSettings"
[dataSource]="data">
</ejs-kanban>
`,
styles: [`
::ng-deep .e-card.e-high-priority {
border-left: 4px solid #d32f2f;
}
::ng-deep .e-card.e-medium-priority {
border-left: 4px solid #f57c00;
}
::ng-deep .e-card.e-low-priority {
border-left: 4px solid #388e3c;
}
`]
})
export class KanbanComponent {
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary',
grabberField: 'Priority'
};
data = [
{ Id: 1, Status: 'Open', Summary: 'Task 1', Priority: 'High' },
{ Id: 2, Status: 'Open', Summary: 'Task 2', Priority: 'Low' }
];
}Custom Status Badge
cardSettings: CardSettingsModel = {
template: `
<div style="padding: 10px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<strong>\${data.Id}</strong>
<span style="
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: bold;
background: \${getStatusColor(data.Priority)};
color: white;">
\${data.Priority}
</span>
</div>
<p style="margin: 0; font-size: 14px;">\${data.Summary}</p>
</div>
`
};
getStatusColor(priority: string): string {
const colors: { [key: string]: string } = {
'High': '#d32f2f',
'Medium': '#f57c00',
'Low': '#388e3c',
'Critical': '#6a1b9a'
};
return colors[priority] || '#333';
}Advanced Features
Multi-Level Filtering
advancedFilter(): void {
this.kanban.filter((card) => {
return (
card.Priority === 'High' &&
card.Status === 'Open' &&
card.Assignee === this.currentUser &&
card.Tags?.includes('urgent')
);
});
}Bulk Operations
bulkUpdatePriority(): void {
const selectedCards = this.kanban.getSelectedCards();
selectedCards.forEach(cardElement => {
const cardId = cardElement.id;
this.kanban.updateCard({
Id: cardId,
Priority: 'High'
});
});
}Export Functionality
exportToCSV(): void {
// Get all data
const data = this.kanban.getCardCount() > 0
? this.kanban.getDataManager().dataSource.json
: [];
// Convert to CSV
const csv = this.convertToCSV(data);
this.downloadCSV(csv);
}
private convertToCSV(data: any[]): string {
const headers = ['ID', 'Summary', 'Status', 'Priority'];
const rows = data.map(item => [item.Id, item.Summary, item.Status, item.Priority]);
return [
headers.join(','),
...rows.map(r => r.join(','))
].join('\n');
}
private downloadCSV(csv: string): void {
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'kanban-export.csv';
a.click();
}Getting Started with Angular Kanban
Table of Contents
Installation
Install the Syncfusion Angular Kanban package using npm:
npm install @syncfusion/ej2-angular-kanbanThis package includes:
- Core Kanban component (
KanbanModule) - Type definitions and models
- Built-in themes (Material, Bootstrap, Fabric, etc.)
- Required dependencies (ej2-base, ej2-buttons, ej2-dropdowns, ej2-popups, ej2-layouts)
Module Setup
Import KanbanModule in your component:
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `<ejs-kanban></ejs-kanban>`
})
export class KanbanComponent {}For traditional NgModule setup:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, KanbanModule],
bootstrap: [AppComponent]
})
export class AppModule {}CSS Imports
Add Kanban CSS styles to styles.css in your project root:
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-dropdowns/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-layouts/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-kanban/styles/material3.css';Available themes:
material3.css(default)material.cssbootstrap5.cssbootstrap4.cssbootstrap.cssfabric.csshigh-contrast.css
Choose one theme based on your design requirements.
Basic Component
The minimal Kanban setup requires three properties:
1. `keyField` - Maps data field to columns 2. `dataSource` - Array of task objects 3. `cardSettings` - Specifies which fields render in cards
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="data"
[cardSettings]="cardSettings">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary'
};
data = [
{ Id: 1, Status: 'Open', Summary: 'Research new technologies' },
{ Id: 2, Status: 'InProgress', Summary: 'Design architecture' },
{ Id: 3, Status: 'Close', Summary: 'Implementation complete' }
];
}Key bindings:
keyField="Status"- Kanban looks for "Status" field in each data objectcardSettings.headerField: 'Id'- Displays card ID at topcardSettings.contentField: 'Summary'- Displays task description
Sample Data
A typical Kanban data object contains:
export const kanbanData = [
{
Id: 1,
Status: 'Open',
Summary: 'Analyze the new requirements gathered from the customer.',
Type: 'Epic',
Priority: 'Critical',
Tags: 'Analyze',
Estimate: 3.5,
Assignee: 'Nancy Davloio',
RankId: 1
},
{
Id: 2,
Status: 'InProgress',
Summary: 'Improve application performance',
Type: 'Epic',
Priority: 'High',
Tags: 'Performance',
Estimate: 5.5,
Assignee: 'Janet Issues',
RankId: 2
},
{
Id: 3,
Status: 'Testing',
Summary: 'Fix the issues reported in the IE browser',
Type: 'Bug',
Priority: 'Medium',
Tags: 'Bug,IE',
Estimate: 2.5,
Assignee: 'Janet Issues',
RankId: 3
},
{
Id: 4,
Status: 'Testing',
Summary: 'Fix the issues reported by the customer.',
Type: 'Bug',
Priority: 'Low',
Tags: 'Customer',
Estimate: 3.5,
Assignee: 'Steven Walker',
RankId: 4
},
{
Id: 5,
Status: 'Close',
Summary: 'Analyze SQL server 2008 connection.',
Type: 'Story',
Priority: 'Critical',
Tags: 'Grid,Sql',
Estimate: 2,
Assignee: 'Michael Suyama',
RankId: 5
}
];Data field mapping:
Id- Unique identifier (used as card header)Status- Column mapping field (Open, InProgress, Testing, Close)Summary- Card content/descriptionType- Optional: Epic, Bug, Story, TaskPriority- Optional: Critical, High, Medium, LowAssignee- Optional: User name (used for swimlanes)Tags- Optional: Comma-separated tagsEstimate- Optional: Story points or time estimateRankId- Optional: Card order within column
First Running Board
Complete example with all essential setup:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
import { CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, KanbanModule],
template: `
<div class="container">
<h1>Project Task Board</h1>
<ejs-kanban
keyField="Status"
[dataSource]="tasks"
[cardSettings]="cardSettings">
<e-columns>
<e-column headerText="📋 To Do" keyField="Open"></e-column>
<e-column headerText="🔨 In Progress" keyField="InProgress"></e-column>
<e-column headerText="✅ Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
</div>
`,
styles: [`
.container {
padding: 20px;
font-family: Arial, sans-serif;
}
h1 {
color: #333;
margin-bottom: 20px;
}
`]
})
export class AppComponent {
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary',
showHeader: true
};
tasks = [
{ Id: 'Task-1', Status: 'Open', Summary: 'Design homepage mockup' },
{ Id: 'Task-2', Status: 'Open', Summary: 'Create database schema' },
{ Id: 'Task-3', Status: 'InProgress', Summary: 'Implement authentication' },
{ Id: 'Task-4', Status: 'InProgress', Summary: 'API endpoint development' },
{ Id: 'Task-5', Status: 'Close', Summary: 'Testing framework setup' },
{ Id: 'Task-6', Status: 'Close', Summary: 'Documentation complete' }
];
}Run the application:
ng serveVisit http://localhost:4200 to see your Kanban board. Cards can be dragged between columns immediately (drag-and-drop is enabled by default).
Next Steps
- To customize cards: Read columns-and-swimlanes.md
- To add data binding: Read cards-and-data-binding.md
- To handle events: Read events.md
- To use methods: Read methods.md
Methods Reference for Angular Kanban
Table of Contents
- Card Operations
- Column Operations
- Selection Operations
- Dialog and UI Operations
- Data and Query Operations
- Lifecycle Methods
Card Operations
addCard(cardData: Record | Record[], index?: number): void
Add one or multiple cards to the Kanban.
import { Component, ViewChild } from '@angular/core';
import { KanbanComponent } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
template: `
<button (click)="addSingleCard()">Add Card</button>
<button (click)="addMultipleCards()">Add Multiple</button>
<ejs-kanban #kanban keyField="Status" [dataSource]="data"></ejs-kanban>
`
})
export class KanbanComponent {
@ViewChild('kanban') kanban: KanbanComponent;
data = [{ Id: 1, Status: 'Open', Summary: 'Task 1' }];
addSingleCard(): void {
this.kanban.addCard({
Id: 2,
Status: 'Open',
Summary: 'New task'
});
}
addMultipleCards(): void {
this.kanban.addCard([
{ Id: 3, Status: 'Open', Summary: 'Task 3' },
{ Id: 4, Status: 'InProgress', Summary: 'Task 4' }
]);
}
// Add at specific position within column
addCardAtIndex(): void {
this.kanban.addCard({
Id: 5,
Status: 'Open',
Summary: 'Task at index 1'
}, 1); // Position 1 in column
}
}deleteCard(cardData: string | number | Record | Record[]): void
Remove card(s) from Kanban.
// Delete by ID
deleteCard(id: string | number): void {
this.kanban.deleteCard(1);
}
// Delete by object
deleteCard(): void {
this.kanban.deleteCard({ Id: 1, Status: 'Open' });
}
// Delete multiple
deleteMultiple(): void {
this.kanban.deleteCard([1, 2, 3]);
}updateCard(cardData: Record, index?: number): void
Update an existing card's data.
updateCard(): void {
this.kanban.updateCard({
Id: 1,
Status: 'InProgress', // Changed from 'Open'
Summary: 'Updated task description'
});
}
// Update at specific position
updateCardAtIndex(): void {
this.kanban.updateCard({
Id: 1,
Status: 'Close',
Summary: 'Completed'
}, 0);
}getCardCount(key?: string): number
Get count of cards in a column or total cards.
// Total cards in board
getTotalCards(): void {
const total = this.kanban.getCardCount();
console.log('Total cards:', total);
}
// Cards in specific column
getColumnCardCount(): void {
const count = this.kanban.getCardCount('InProgress');
console.log('In Progress cards:', count);
}Column Operations
addColumn(columnOptions: ColumnsModel, index: number): void
Add a new column to the Kanban.
import { ColumnsModel } from '@syncfusion/ej2-angular-kanban';
addColumn(): void {
const newColumn: ColumnsModel = {
headerText: 'Testing',
keyField: 'Testing',
allowToggle: true,
maxCount: 5
};
this.kanban.addColumn(newColumn, 2); // Insert at position 2
}removeColumn(key: string | number): void
Remove a column from the Kanban.
removeColumn(): void {
this.kanban.removeColumn('Testing'); // Remove by keyField
}hideColumn(key: string | number): void
Hide a column without removing it.
hideColumn(): void {
this.kanban.hideColumn('Close');
}showColumn(key: string | number): void
Show a previously hidden column.
showColumn(): void {
this.kanban.showColumn('Close');
}toggleColumnCollapse(key: string | number): void
Collapse or expand a column.
toggleColumn(): void {
this.kanban.toggleColumnCollapse('InProgress');
}Selection Operations
getSelectedCards(): HTMLElement[]
Get all currently selected card elements.
getSelected(): void {
const selectedCards = this.kanban.getSelectedCards();
console.log('Selected card count:', selectedCards.length);
selectedCards.forEach(card => {
console.log('Card ID:', card.id);
});
}selectCard(cardId: string | number): void
Programmatically select a card.
selectCard(): void {
this.kanban.selectCard(1);
}
// Select multiple
selectMultiple(): void {
this.kanban.selectCard(1);
this.kanban.selectCard(2, true); // true = add to selection
}deselectCard(cardId: string | number): void
Deselect a card.
deselectCard(): void {
this.kanban.deselectCard(1);
}Dialog and UI Operations
openDialog(data?: Record, action?: string): void
Open add/edit dialog programmatically.
// Open new card dialog
openNewCardDialog(): void {
this.kanban.openDialog();
}
// Open edit dialog for existing card
openEditDialog(): void {
this.kanban.openDialog({
Id: 1,
Status: 'Open',
Summary: 'Task to edit'
}, 'Edit');
}
// Pre-fill new card dialog
openWithDefaults(): void {
this.kanban.openDialog({
Status: 'Open', // Pre-select status
Priority: 'High' // Pre-select priority
});
}closeDialog(): void
Close currently open dialog.
closeCurrentDialog(): void {
this.kanban.closeDialog();
}refresh(): void
Refresh/redraw the entire Kanban board.
refreshBoard(): void {
this.kanban.refresh();
}print(): void
Print the Kanban board.
printBoard(): void {
this.kanban.print();
}Data and Query Operations
getSwimlaneData(key: string): Record[]
Get all cards in a swimlane by key.
getSwimlaneCards(): void {
const nancyCards = this.kanban.getSwimlaneData('Nancy');
console.log('Nancy\'s tasks:', nancyCards);
console.log('Task count:', nancyCards.length);
}search(searchString: string): void
Search cards by string.
searchCards(): void {
this.kanban.search('authentication'); // Find cards with 'authentication'
}
// Search with input
onSearchInput(searchTerm: string): void {
this.kanban.search(searchTerm);
}filter(predicateFn: Function | string, value?: string): void
Filter cards with predicate function.
// Filter by field value
filterByPriority(): void {
this.kanban.filter('Priority', 'High');
}
// Filter with predicate
filterByCondition(): void {
this.kanban.filter((card) => {
return card.Priority === 'High' && card.Assignee === 'Nancy';
});
}sort(predicateFn: Function | string, direction?: 'Ascending' | 'Descending'): void
Sort cards in columns.
// Sort by field
sortByPriority(): void {
this.kanban.sort('Priority', 'Descending');
}
// Sort with custom function
sortCustom(): void {
this.kanban.sort((a, b) => {
return b.Estimate - a.Estimate; // Descending by estimate
});
}Lifecycle Methods
getDataManager(): DataManager
Get the DataManager instance.
getManager(): void {
const dataManager = this.kanban.getDataManager();
console.log('DataManager:', dataManager);
}destroy(): void
Destroy Kanban instance and cleanup.
ngOnDestroy(): void {
if (this.kanban) {
this.kanban.destroy();
}
}---
Complete Methods Example
import { Component, ViewChild } from '@angular/core';
import { KanbanComponent, KanbanModule, ColumnsModel } from '@syncfusion/ej2-angular-kanban';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-kanban-methods',
standalone: true,
imports: [CommonModule, FormsModule, KanbanModule],
template: `
<div style="margin-bottom: 20px;">
<h3>Card Operations</h3>
<button (click)="addNewCard()">Add Card</button>
<button (click)="updateSelectedCard()">Update Selected</button>
<button (click)="deleteSelectedCard()">Delete Selected</button>
<button (click)="printBoard()">Print</button>
</div>
<div style="margin-bottom: 20px;">
<h3>Column Operations</h3>
<button (click)="addTestingColumn()">Add Testing Column</button>
<button (click)="hideClosedColumn()">Hide Closed</button>
<button (click)="showClosedColumn()">Show Closed</button>
</div>
<div style="margin-bottom: 20px;">
<h3>Search and Filter</h3>
<input
[(ngModel)]="searchTerm"
placeholder="Search cards"
(input)="performSearch()">
<button (click)="clearSearch()">Clear</button>
</div>
<ejs-kanban
#kanban
keyField="Status"
[dataSource]="data">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanMethodsComponent {
@ViewChild('kanban') kanban: KanbanComponent;
searchTerm: string = '';
data = [
{ Id: 1, Status: 'Open', Summary: 'Design homepage' },
{ Id: 2, Status: 'Open', Summary: 'Setup database' },
{ Id: 3, Status: 'InProgress', Summary: 'API development' },
{ Id: 4, Status: 'Close', Summary: 'Deploy app' }
];
addNewCard(): void {
this.kanban.addCard({
Id: 5,
Status: 'Open',
Summary: 'New task'
});
}
updateSelectedCard(): void {
this.kanban.updateCard({
Id: 1,
Status: 'InProgress',
Summary: 'Design complete - moving to development'
});
}
deleteSelectedCard(): void {
this.kanban.deleteCard(1);
}
addTestingColumn(): void {
this.kanban.addColumn({
headerText: 'Testing',
keyField: 'Testing'
}, 2);
}
hideClosedColumn(): void {
this.kanban.hideColumn('Close');
}
showClosedColumn(): void {
this.kanban.showColumn('Close');
}
performSearch(): void {
if (this.searchTerm) {
this.kanban.search(this.searchTerm);
}
}
clearSearch(): void {
this.searchTerm = '';
this.kanban.search('');
}
printBoard(): void {
this.kanban.print();
}
}Migration and Best Practices for Angular Kanban
Table of Contents
- EJ1 to EJ2 Migration
- Performance Optimization
- Virtual Scrolling
- Common Patterns
- Best Practices
- Troubleshooting
EJ1 to EJ2 Migration
Breaking Changes
| EJ1 | EJ2 | Migration Path |
|---|---|---|
| jQuery plugin | Angular component | Use <ejs-kanban> selector |
dataSource | [dataSource] | Bind property with [] |
cardSettings | [cardSettings] | Use cardSettings model |
Events suffix: Begin, End | actionBegin, actionComplete | Rename events |
| Properties: camelCase | Property binding [] | Use Angular bindings |
| DOM methods | EJ2 methods | Use component ref methods |
EJ1 Code
// EJ1 - jQuery
$('#kanban').ejKanban({
dataSource: kanbanData,
keyField: 'Status',
cardSettings: {
headerField: 'Id',
contentField: 'Summary'
},
cardRendered: function(args) {
console.log('Card rendered');
}
});EJ2 Code
// EJ2 - Angular
import { Component, ViewChild } from '@angular/core';
import { KanbanComponent, KanbanModule, CardSettingsModel } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
#kanban
keyField="Status"
[dataSource]="kanbanData"
[cardSettings]="cardSettings"
(cardRendered)="onCardRendered($event)">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="Done" keyField="Close"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanComponent {
@ViewChild('kanban') kanban: KanbanComponent;
kanbanData = [];
cardSettings: CardSettingsModel = {
headerField: 'Id',
contentField: 'Summary'
};
onCardRendered(args: any): void {
console.log('Card rendered');
}
}Migration Steps
1. Remove jQuery dependency
npm uninstall jquery
npm install @syncfusion/ej2-angular-kanban2. Update component import
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';3. Convert template to Angular syntax
- Replace jQuery selectors with component refs
- Use property binding
[prop]="value" - Use event binding
(event)="handler()"
4. Update API calls
// EJ1
$('#kanban').ejKanban('addCard', cardData);
// EJ2
this.kanban.addCard(cardData);5. Test thoroughly
- Verify all features work
- Test drag-and-drop
- Validate data binding
- Check events
Performance Optimization
1. Lazy Loading Data
@Component({
selector: 'app-kanban-lazy',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
#kanban
keyField="Status"
[dataSource]="data">
</ejs-kanban>
`
})
export class KanbanLazyComponent implements OnInit {
@ViewChild('kanban') kanban: KanbanComponent;
data: any[] = [];
ngOnInit(): void {
this.loadInitialData();
}
loadInitialData(): void {
// Load first 100 cards
this.api.getCards(0, 100).subscribe(cards => {
this.data = cards;
});
}
onScroll(event: any): void {
// Load more when scrolling near bottom
if (event.scrollTop > event.scrollHeight - 500) {
this.loadMoreData();
}
}
loadMoreData(): void {
this.api.getCards(this.data.length, 100).subscribe(cards => {
this.data = [...this.data, ...cards];
});
}
}2. Optimize Change Detection
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-kanban-optimized',
standalone: true,
imports: [KanbanModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ejs-kanban [dataSource]="data"></ejs-kanban>`
})
export class KanbanOptimizedComponent {
data = [];
constructor(private cdr: ChangeDetectorRef) {}
updateData(newData: any[]): void {
this.data = newData;
this.cdr.markForCheck(); // Manually trigger change detection
}
}3. Debounce Event Handlers
import { Component } from '@angular/core';
import { debounceTime, Subject } from 'rxjs';
@Component({
selector: 'app-kanban-debounce',
standalone: true,
template: `<ejs-kanban (dragStop)="onDragStop($event)"></ejs-kanban>`
})
export class KanbanDebounceComponent {
private dragStopSubject = new Subject<any>();
constructor() {
this.dragStopSubject.pipe(debounceTime(500)).subscribe(args => {
this.saveCardStatus(args);
});
}
onDragStop(args: any): void {
this.dragStopSubject.next(args); // Debounced save
}
private saveCardStatus(args: any): void {
console.log('Saving card after drag');
// API call here
}
}Virtual Scrolling
Enable virtual scrolling for large datasets (1000+ cards):
import { Component } from '@angular/core';
import { KanbanModule } from '@syncfusion/ej2-angular-kanban';
@Component({
selector: 'app-kanban-virtual',
standalone: true,
imports: [KanbanModule],
template: `
<ejs-kanban
keyField="Status"
[dataSource]="largeDataset"
[allowVirtualization]="true"
height="600px">
<e-columns>
<e-column headerText="To Do" keyField="Open"></e-column>
<e-column headerText="In Progress" keyField="InProgress"></e-column>
</e-columns>
</ejs-kanban>
`
})
export class KanbanVirtualComponent {
largeDataset = this.generateLargeDataset();
private generateLargeDataset(): any[] {
const dataset = [];
for (let i = 1; i <= 5000; i++) {
dataset.push({
Id: i,
Status: ['Open', 'InProgress', 'Testing', 'Close'][i % 4],
Summary: `Task ${i}`,
Priority: ['High', 'Medium', 'Low'][i % 3]
});
}
return dataset;
}
}Common Patterns
Pattern 1: Master-Detail View
@Component({
selector: 'app-kanban-master-detail',
standalone: true,
template: `
<div style="display: flex;">
<div style="flex: 1;">
<ejs-kanban
(cardClick)="selectCard($event)">
</ejs-kanban>
</div>
<div style="flex: 1; padding: 20px; border-left: 1px solid #ddd;">
<div *ngIf="selectedCard">
<h3>{{ selectedCard.Id }}</h3>
<p>{{ selectedCard.Summary }}</p>
<p>Status: {{ selectedCard.Status }}</p>
<p>Priority: {{ selectedCard.Priority }}</p>
</div>
</div>
</div>
`
})
export class KanbanMasterDetailComponent {
selectedCard: any;
selectCard(args: any): void {
this.selectedCard = args.data;
}
}Pattern 2: Real-time Collaboration
@Component({
selector: 'app-kanban-collab',
standalone: true,
template: `<ejs-kanban (dragStop)="onDragStop($event)"></ejs-kanban>`
})
export class KanbanCollabComponent {
constructor(private websocket: WebSocketService) {
// Listen for remote updates
this.websocket.on('cardMoved', (data) => {
this.updateCardRemotely(data);
});
}
onDragStop(args: any): void {
// Broadcast card movement to other users
this.websocket.emit('cardMoved', {
cardId: args.data.Id,
newStatus: args.data.Status,
user: this.currentUser
});
}
updateCardRemotely(data: any): void {
// Update UI when another user moves card
console.log(`${data.user} moved card ${data.cardId}`);
}
}Pattern 3: Undo/Redo
class UndoRedoManager {
private history: BoardState[] = [];
private historyIndex = -1;
recordState(state: BoardState): void {
this.history.splice(this.historyIndex + 1);
this.history.push(state);
this.historyIndex++;
}
undo(): BoardState | null {
if (this.historyIndex > 0) {
return this.history[--this.historyIndex];
}
return null;
}
redo(): BoardState | null {
if (this.historyIndex < this.history.length - 1) {
return this.history[++this.historyIndex];
}
return null;
}
}
interface BoardState {
timestamp: Date;
data: any[];
action: string;
}Best Practices
1. Use OnPush Change Detection
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})2. Track Changes with TrackBy
trackByCardId(index: number, card: any): any {
return card.Id;
}3. Unsubscribe in ngOnDestroy
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}4. Validate Data Before Updates
updateCard(card: any): void {
if (this.isValidCard(card)) {
this.kanban.updateCard(card);
}
}5. Handle Errors Gracefully
addCard(card: any): void {
this.api.addCard(card).subscribe(
() => this.kanban.addCard(card),
(error) => console.error('Failed to add card:', error)
);
}6. Use Async Pipe
<ejs-kanban [dataSource]="data$ | async"></ejs-kanban>Troubleshooting
Issue: Cards Not Displaying
Cause: Missing keyField property
Solution:
<ejs-kanban keyField="Status"></ejs-kanban> // Required!Issue: Drag-and-Drop Not Working
Cause: allowDragAndDrop disabled
Solution:
<ejs-kanban [allowDragAndDrop]="true"></ejs-kanban>Issue: Styles Not Applied
Cause: Missing CSS imports
Solution:
@import '../node_modules/@syncfusion/ej2-kanban/styles/material3.css';Issue: Memory Leak
Cause: Unmanaged subscriptions
Solution:
ngOnDestroy(): void {
this.cardClicked$.unsubscribe();
this.kanban.destroy();
}Issue: Slow with Large Datasets
Cause: No virtual scrolling
Solution:
<ejs-kanban
[allowVirtualization]="true"
height="600px">
</ejs-kanban>Issue: Column Not Showing Items
Cause: keyField mismatch
Solution:
// Ensure data field value matches column keyField
{ Id: 1, Status: 'Open' } // Status='Open'
<e-column keyField="Open"></e-column> // keyField='Open' ✓Performance Metrics
| Scenario | Cards | Load Time |
|---|---|---|
| Without virtual scrolling | 100 | ~200ms |
| Without virtual scrolling | 1000 | ~2000ms ⚠️ |
| With virtual scrolling | 1000 | ~500ms ✓ |
| With virtual scrolling | 5000 | ~800ms ✓ |
Testing Checklist
- [ ] Drag-and-drop works smoothly
- [ ] Column changes update correctly
- [ ] Data binding refreshes properly
- [ ] Keyboard navigation works
- [ ] Events fire correctly
- [ ] Performance acceptable (< 500ms)
- [ ] Mobile responsive
- [ ] Accessibility compliant
- [ ] No console errors
- [ ] No memory leaks