Virtual scroll in Angular Gantt component
15 Nov 202524 minutes to read
Virtual scrolling in the Angular Gantt component enhances performance by rendering only visible tasks and timeline segments, minimizing DOM operations for large datasets or extensive timelines. It includes row virtualization for handling thousands of tasks (e.g., 10,000 tasks in a project) and timeline virtualization for wide timelines (e.g., multi-year projects), both requiring VirtualScrollService injection. Row virtualization renders tasks within the viewport, while timeline virtualization loads timeline cells on-demand during horizontal scrolling, ensuring efficient rendering for complex project management.
Configure row virtualization
Row virtualization, enabled by setting enableVirtualization to true, renders only tasks visible in the Gantt’s viewport, determined by the height property in pixels (e.g., “600px”). All tasks are fetched initially but rendered on-demand during vertical scrolling, reducing load times for large datasets. For example, a project with 10,000 tasks renders only the 50 visible tasks, improving performance. Inject VirtualScrollService in the component’s providers to enable this feature. Ensure the height property is set explicitly to control the viewport size.
The following example enables row virtualization for a large dataset:
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GanttModule } from '@syncfusion/ej2-angular-gantt'
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { Gantt } from '@syncfusion/ej2-gantt';
import { GanttComponent, VirtualScrollService } from '@syncfusion/ej2-angular-gantt';
@Component({
imports: [
GanttModule
],
providers: [VirtualScrollService],
standalone: true,
selector: 'app-root',
template:
`<ejs-gantt id="ganttDefault" height="450px" [dataSource]="data" [taskFields]="taskSettings" [treeColumnIndex]="1"
[splitterSettings]="splitterSettings" [columns]="columns" [labelSettings]="labelSettings"
[allowSelection]="true" [enableVirtualization]="true" [highlightWeekends]="true"></ejs-gantt>`,
encapsulation: ViewEncapsulation.None
})
export class AppComponent{
// Data for Gantt
public data?: object[];
public taskSettings?: object;
public splitterSettings?: object;
public columns?: object[];
public labelSettings?: object;
public ngOnInit(): void {
let tempData: any[] = [
{
TaskID: 1, TaskName: 'Product concept',StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019'),
parentID: 0
},
{
TaskID: 2, TaskName: 'Defining the product and its usage', StartDate: new Date('04/02/2019'),
Duration: 3, Progress: 30, parentID: 1
},
{
TaskID: 3, TaskName: 'Defining target audience', StartDate: new Date('04/02/2019'),
parentID: 1, Duration: 3
},
{
TaskID: 4, TaskName: 'Prepare product sketch and notes', StartDate: new Date('04/05/2019'),
Duration: 2, parentID: 1, Progress: 30
},
{
TaskID: 5, TaskName: 'Concept approval', StartDate: new Date('04/08/2019'),
parentID: 0, Duration: 0
},
{
TaskID: 6, TaskName: 'Market research', StartDate: new Date('04/02/2019'),
parentID: 0, EndDate: new Date('04/21/2019')
},
{
TaskID: 7, TaskName: 'Demand analysis', StartDate: new Date('04/04/2019'),
EndDate: new Date('04/21/2019'), parentID: 6
},
{
TaskID: 8, TaskName: 'Customer strength', StartDate: new Date('04/09/2019'),
Duration: 4, parentID: 7, Progress: 30
},
{
TaskID: 9, TaskName: 'Market opportunity analysis', StartDate: new Date('04/09/2019'),
Duration: 4, parentID: 7
},
{
TaskID: 10, TaskName: 'Competitor analysis', StartDate: new Date('04/15/2019'),
Duration: 4, parentID: 6, Progress: 30
},
{
TaskID: 11, TaskName: 'Product strength analsysis', StartDate: new Date('04/15/2019'),
Duration: 4, parentID: 6
},
{
TaskID: 12, TaskName: 'Research complete', StartDate: new Date('04/18/2019'),
Duration: 0, parentID: 6
},
{
TaskID: 13, TaskName: 'Product design and development', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019')
},
{
TaskID: 14, TaskName: 'Functionality design', StartDate: new Date('04/19/2019'),
Duration: 3, parentID: 13, Progress: 30
},
{
TaskID: 15, TaskName: 'Quality design', StartDate: new Date('04/19/2019'),
Duration: 3, parentID: 13
},
{
TaskID: 16, TaskName: 'Define reliability', StartDate: new Date('04/24/2019'),
Duration: 2, Progress: 30, parentID: 13
},
{
TaskID: 17, TaskName: 'Identifying raw materials', StartDate: new Date('04/24/2019'),
Duration: 2, parentID: 13
},
{
TaskID: 18, TaskName: 'Define cost plan', StartDate: new Date('04/04/2019'),
parentID: 13, EndDate: new Date('04/21/2019')
},
{
TaskID: 19, TaskName: 'Manufacturing cost', StartDate: new Date('04/26/2019'),
Duration: 2, Progress: 30, parentID: 18
},
{
TaskID: 20, TaskName: 'Selling cost', StartDate: new Date('04/26/2019'),
Duration: 2, parentID: 18
},
{
TaskID: 21, TaskName: 'Development of the final design', StartDate: new Date('04/30/2019'),
parentID: 13, EndDate: new Date('04/21/2019')
},
{
TaskID: 22, TaskName: 'Defining dimensions and package volume', StartDate: new Date('04/30/2019'),
Duration: 2, parentID: 21, Progress: 30
},
{
TaskID: 23, TaskName: 'Develop design to meet industry standards', StartDate: new Date('05/02/2019'),
Duration: 2, parentID: 21
},
{
TaskID: 24, TaskName: 'Include all the details', StartDate: new Date('05/06/2019'),
Duration: 3, parentID: 21
},
{
TaskID: 25, TaskName: 'CAD computer-aided design', StartDate: new Date('05/09/2019'),
Duration: 3, parentID: 13, Progress: 30
},
{
TaskID: 26, TaskName: 'CAM computer-aided manufacturing', StartDate: new Date('09/14/2019'),
Duration: 3, parentID: 13
},
{
TaskID: 27, TaskName: 'Design complete', StartDate: new Date('05/16/2019'),
Duration: 0, parentID: 13
},
{
TaskID: 28, TaskName: 'Prototype testing', StartDate: new Date('05/17/2019'),
Duration: 4, Progress: 30, parentID: 0
},
{
TaskID: 29, TaskName: 'Include feedback', StartDate: new Date('05/17/2019'),
Duration: 4, parentID: 0
},
{
TaskID: 30, TaskName: 'Manufacturing', StartDate: new Date('05/23/2019'),
Duration: 5, Progress: 30, parentID: 0
},
{
TaskID: 31, TaskName: 'Assembling materials to finsihed goods', StartDate: new Date('05/30/2019'),
Duration: 5, parentID: 0
},
{
TaskID: 32, TaskName: 'Feedback and testing', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019'),
},
{
TaskID: 33, TaskName: 'Internal testing and feedback', StartDate: new Date('06/06/2019'),
Duration: 3, parentID: 32, Progress: 45
},
{
TaskID: 34, TaskName: 'Customer testing and feedback', StartDate: new Date('06/11/2019'),
Duration: 3, parentID: 32, Progress: 50
},
{
TaskID: 35, TaskName: 'Final product development', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019'),
},
{
TaskID: 36, TaskName: 'Important improvements', StartDate: new Date('06/14/2019'),
Duration: 4, Progress: 30, parentID: 35
},
{
TaskID: 37, TaskName: 'Address any unforeseen issues', StartDate: new Date('06/14/2019'),
Duration: 4, Progress: 30, parentID: 35
},
{
TaskID: 38, TaskName: 'Final product', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019'),
},
{
TaskID: 39, TaskName: 'Branding product', StartDate: new Date('06/20/2019'),
Duration: 4, parentID: 38
},
{
TaskID: 40, TaskName: 'Marketing and presales', StartDate: new Date('06/26/2019'), Duration: 4,
Progress: 30, parentID: 38
}
];
let virtualData: any[] = [];
let projId: number = 1;
for (let i: number = 0; i < 50; i++) {
let x: number = virtualData.length + 1;
let parent: any = {};
/* tslint:disable:no-string-literal */
parent['TaskID'] = x;
parent['TaskName'] = 'Project ' + (i + 1);
virtualData.push(parent);
for (let j: number = 0; j < tempData.length; j++) {
let subtasks: any = {};
/* tslint:disable:no-string-literal */
subtasks['TaskID'] = tempData[j].TaskID + x;
subtasks['TaskName'] = tempData[j].TaskName;
subtasks['StartDate'] = tempData[j].StartDate;
subtasks['Duration'] = tempData[j].Duration;
subtasks['Progress'] = tempData[j].Progress;
subtasks['parentID'] = tempData[j].parentID + x;
virtualData.push(subtasks);
}
}
this.data = virtualData,
this.taskSettings = {
id: 'TaskID',
name: 'TaskName',
startDate: 'StartDate',
endDate: 'EndDate',
duration: 'Duration',
progress: 'Progress',
parentID: 'parentID'
};
this.columns = [
{ field: 'TaskID' },
{ field: 'TaskName' },
{ field: 'StartDate' },
{ field: 'Duration' },
{ field: 'Progress' }
];
this.splitterSettings = {
columnIndex: 2
};
this.labelSettings = {
leftLabel: 'TaskName',
taskLabel: 'Progress'
};
}
}import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));Configure timeline virtualization
Timeline virtualization, enabled by setting enableTimelineVirtualization to true, renders three times the Gantt’s width initially, loading additional timeline cells during horizontal scrolling. This optimizes performance for wide timelines, such as multi-year projects, by rendering only visible segments. It depends on timelineSettings for scale (e.g., monthly or daily tiers). Inject VirtualScrollService to enable this feature.
The following example enables timeline virtualization for a wide timeline:
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { GanttModule } from '@syncfusion/ej2-angular-gantt'
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { GanttComponent, VirtualScrollService } from '@syncfusion/ej2-angular-gantt';
import { ToolbarItem, EditSettingsModel } from '@syncfusion/ej2-angular-gantt';
@Component({
imports: [
GanttModule
],
providers: [VirtualScrollService],
standalone: true,
selector: 'app-root',
template:
`<ejs-gantt id="ganttDefault" height="450px" [dataSource]="data" [taskFields]="taskSettings" [treeColumnIndex]="1"
[splitterSettings]="splitterSettings" [columns]="columns" [labelSettings]="labelSettings" [projectStartDate]="projectStartDate" [projectEndDate]="projectEndDate"
[allowSelection]="true" [enableVirtualization]="true" [enableTimelineVirtualization]="true" [autoCalculateDateScheduling]="false" [editSettings] = "editSettings" [highlightWeekends]="true"></ejs-gantt>`,
encapsulation: ViewEncapsulation.None
})
export class AppComponent{
// Data for Gantt
public data?: object[];
public taskSettings?: object;
public splitterSettings?: object;
public columns?: object[];
public editSettings?: EditSettingsModel;
public toolbar?: ToolbarItem[];
public labelSettings?: object;
public projectStartDate?: Date;
public projectEndDate?: Date;
public ngOnInit(): void {
let tempData: any[] = [
{
TaskID: 1, TaskName: 'Product concept',StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019'),
parentID: 0
},
{
TaskID: 2, TaskName: 'Defining the product and its usage', StartDate: new Date('04/02/2019'),
Duration: 3, Progress: 30, parentID: 1
},
{
TaskID: 3, TaskName: 'Defining target audience', StartDate: new Date('04/02/2019'),
parentID: 1, Duration: 3
},
{
TaskID: 4, TaskName: 'Prepare product sketch and notes', StartDate: new Date('04/05/2019'),
Duration: 2, parentID: 1, Progress: 30
},
{
TaskID: 5, TaskName: 'Concept approval', StartDate: new Date('04/08/2019'),
parentID: 0, Duration: 0
},
{
TaskID: 6, TaskName: 'Market research', StartDate: new Date('04/02/2019'),
parentID: 0, EndDate: new Date('04/21/2019')
},
{
TaskID: 7, TaskName: 'Demand analysis', StartDate: new Date('04/04/2019'),
EndDate: new Date('04/21/2019'), parentID: 6
},
{
TaskID: 8, TaskName: 'Customer strength', StartDate: new Date('04/09/2019'),
Duration: 4, parentID: 7, Progress: 30
},
{
TaskID: 9, TaskName: 'Market opportunity analysis', StartDate: new Date('04/09/2019'),
Duration: 4, parentID: 7
},
{
TaskID: 10, TaskName: 'Competitor analysis', StartDate: new Date('04/15/2019'),
Duration: 4, parentID: 6, Progress: 30
},
{
TaskID: 11, TaskName: 'Product strength analsysis', StartDate: new Date('04/15/2019'),
Duration: 4, parentID: 6
},
{
TaskID: 12, TaskName: 'Research complete', StartDate: new Date('04/18/2019'),
Duration: 0, parentID: 6
},
{
TaskID: 13, TaskName: 'Product design and development', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019')
},
{
TaskID: 14, TaskName: 'Functionality design', StartDate: new Date('04/19/2019'),
Duration: 3, parentID: 13, Progress: 30
},
{
TaskID: 15, TaskName: 'Quality design', StartDate: new Date('04/19/2019'),
Duration: 3, parentID: 13
},
{
TaskID: 16, TaskName: 'Define reliability', StartDate: new Date('04/24/2019'),
Duration: 2, Progress: 30, parentID: 13
},
{
TaskID: 17, TaskName: 'Identifying raw materials', StartDate: new Date('04/24/2019'),
Duration: 2, parentID: 13
},
{
TaskID: 18, TaskName: 'Define cost plan', StartDate: new Date('04/04/2019'),
parentID: 13, EndDate: new Date('04/21/2019')
},
{
TaskID: 19, TaskName: 'Manufacturing cost', StartDate: new Date('04/26/2019'),
Duration: 2, Progress: 30, parentID: 18
},
{
TaskID: 20, TaskName: 'Selling cost', StartDate: new Date('04/26/2019'),
Duration: 2, parentID: 18
},
{
TaskID: 21, TaskName: 'Development of the final design', StartDate: new Date('04/30/2019'),
parentID: 13, EndDate: new Date('04/21/2019')
},
{
TaskID: 22, TaskName: 'Defining dimensions and package volume', StartDate: new Date('04/30/2019'),
Duration: 2, parentID: 21, Progress: 30
},
{
TaskID: 23, TaskName: 'Develop design to meet industry standards', StartDate: new Date('05/02/2019'),
Duration: 2, parentID: 21
},
{
TaskID: 24, TaskName: 'Include all the details', StartDate: new Date('05/06/2019'),
Duration: 3, parentID: 21
},
{
TaskID: 25, TaskName: 'CAD computer-aided design', StartDate: new Date('05/09/2019'),
Duration: 3, parentID: 13, Progress: 30
},
{
TaskID: 26, TaskName: 'CAM computer-aided manufacturing', StartDate: new Date('09/14/2019'),
Duration: 3, parentID: 13
},
{
TaskID: 27, TaskName: 'Design complete', StartDate: new Date('05/16/2019'),
Duration: 0, parentID: 13
},
{
TaskID: 28, TaskName: 'Prototype testing', StartDate: new Date('05/17/2019'),
Duration: 4, Progress: 30, parentID: 0
},
{
TaskID: 29, TaskName: 'Include feedback', StartDate: new Date('05/17/2019'),
Duration: 4, parentID: 0
},
{
TaskID: 30, TaskName: 'Manufacturing', StartDate: new Date('05/23/2019'),
Duration: 5, Progress: 30, parentID: 0
},
{
TaskID: 31, TaskName: 'Assembling materials to finsihed goods', StartDate: new Date('05/30/2019'),
Duration: 5, parentID: 0
},
{
TaskID: 32, TaskName: 'Feedback and testing', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019'),
},
{
TaskID: 33, TaskName: 'Internal testing and feedback', StartDate: new Date('06/06/2019'),
Duration: 3, parentID: 32, Progress: 45
},
{
TaskID: 34, TaskName: 'Customer testing and feedback', StartDate: new Date('06/11/2019'),
Duration: 3, parentID: 32, Progress: 50
},
{
TaskID: 35, TaskName: 'Final product development', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019'),
},
{
TaskID: 36, TaskName: 'Important improvements', StartDate: new Date('06/14/2019'),
Duration: 4, Progress: 30, parentID: 35
},
{
TaskID: 37, TaskName: 'Address any unforeseen issues', StartDate: new Date('06/14/2019'),
Duration: 4, Progress: 30, parentID: 35
},
{
TaskID: 38, TaskName: 'Final product', StartDate: new Date('04/04/2019'),
parentID: 0, EndDate: new Date('04/21/2019'),
},
{
TaskID: 39, TaskName: 'Branding product', StartDate: new Date('06/20/2019'),
Duration: 4, parentID: 38
},
{
TaskID: 40, TaskName: 'Marketing and presales', StartDate: new Date('06/26/2019'), Duration: 4,
Progress: 30, parentID: 38
}
];
let virtualData: any[] = [];
let projId: number = 1;
for (let i: number = 0; i < 50; i++) {
let x: number = virtualData.length + 1;
let parent: any = {};
/* tslint:disable:no-string-literal */
parent['TaskID'] = x;
parent['TaskName'] = 'Project ' + (i + 1);
virtualData.push(parent);
for (let j: number = 0; j < tempData.length; j++) {
let subtasks: any = {};
/* tslint:disable:no-string-literal */
subtasks['TaskID'] = tempData[j].TaskID + x;
subtasks['TaskName'] = tempData[j].TaskName;
subtasks['StartDate'] = tempData[j].StartDate;
subtasks['Duration'] = tempData[j].Duration;
subtasks['Progress'] = tempData[j].Progress;
subtasks['parentID'] = tempData[j].parentID + x;
virtualData.push(subtasks);
}
}
this.data = virtualData,
this.taskSettings = {
id: 'TaskID',
name: 'TaskName',
startDate: 'StartDate',
endDate: 'EndDate',
duration: 'Duration',
progress: 'Progress',
parentID: 'parentID'
};
this.columns = [
{ field: 'TaskID' },
{ field: 'TaskName' },
{ field: 'StartDate' },
{ field: 'Duration' },
{ field: 'Progress' }
];
this.splitterSettings = {
columnIndex: 2
};
this.editSettings = {
allowAdding: true,
allowEditing: true,
allowDeleting: true,
allowTaskbarEditing: true,
showDeleteConfirmDialog: true
},
this.toolbar = ['Add', 'Cancel', 'CollapseAll', 'Delete', 'Edit', 'ExpandAll', 'NextTimeSpan', 'PrevTimeSpan', 'Search', 'Update', 'Indent', 'Outdent']
this.labelSettings = {
leftLabel: 'TaskName',
taskLabel: 'Progress'
};
this.projectEndDate = new Date('04/01/2019');
this.projectStartDate = new Date('12/31/2025');
}
}import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));Virtual scroll limitations
Virtual scrolling has the following constraints:
- Incompatible with enableImmutableMode, as both use different rendering optimizations.
- Cell selection is not persisted due to on-demand rendering.
- Browser height limits restrict the maximum number of records in row virtualization.
- The height property must be set in pixels for row virtualization to define the viewport size.
- Set a static height for the Gantt chart or its parent container; 100% height only works if both the component and its parent have explicit static heights.
- With virtualization enabled, data is rendered in pages. When scrolling to load the next set of records, only the current page’s data is available to Gantt’s public methods. If a record is selected during this process, only the visible page records are returned. This behavior occurs because the Gantt chart does not retain data from all pages in memory, optimizing performance by loading only the required set.