Angular ApexGantt

Installation

npm install ngx-apexgantt apexgantt
# or
yarn add ngx-apexgantt apexgantt

Requires Angular 18, 19, 20, 21 or 22 (earlier ngx-apexgantt releases support Angular 18 only). apexgantt is a peer dependency: this page describes the ngx-apexgantt release built for apexgantt 3.20.0, which requires apexgantt 3.20.0 or later. Notes mark what differs on earlier releases.

Demo

View Demo Project created using ngx-apexgantt

License Setup

ApexGantt is included in the Premium plan and above, and there is no free tier. Without a key the chart renders with a trial watermark. Set your license key once at application start, with either of the two options below. A key set later still removes the watermark from charts already on the page (apexgantt 3.17.0 and later); setting it first avoids a one-frame watermark. From apexgantt 3.20.0 the key's plan must be Premium or higher.

Option 1: Angular Provider (Recommended)

provideApexGanttLicense applies the key when the application injector is created, before any chart renders. It is exported from the release built for apexgantt 3.20.0; earlier releases did not export it, so use Option 2 with them.

Standalone App:

// app.config.ts
import { ApplicationConfig } from "@angular/core";
import { provideApexGanttLicense } from "ngx-apexgantt";

export const appConfig: ApplicationConfig = {
  providers: [
    provideApexGanttLicense("your-license-key-here"),
    // other providers...
  ],
};

Module-based App:

// app.module.ts
import { NgModule } from "@angular/core";
import { provideApexGanttLicense } from "ngx-apexgantt";

@NgModule({
  providers: [provideApexGanttLicense("your-license-key-here")],
})
export class AppModule {}

Option 2: Static Method

// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { setApexGanttLicense } from "ngx-apexgantt";
import { AppComponent } from "./app/app.component";
import { appConfig } from "./app/app.config";

// set license before bootstrapping
setApexGanttLicense("your-license-key-here");

bootstrapApplication(AppComponent, appConfig);

Quick Start

Standalone Component

import { Component } from "@angular/core";
import {
  NgxApexGanttComponent,
  TaskInput,
  TaskUpdateSuccessEventDetail,
} from "ngx-apexgantt";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [NgxApexGanttComponent],
  template: `
    <ngx-apexgantt
      [tasks]="tasks"
      [pixelsPerDay]="25.7"
      [height]="'500px'"
      (taskUpdateSuccess)="onTaskUpdate($event)"
    >
    </ngx-apexgantt>
  `,
})
export class AppComponent {
  tasks: TaskInput[] = [
    {
      id: "task-1",
      name: "Project Planning",
      startTime: "01-01-2024",
      endTime: "01-08-2024",
      progress: 75,
    },
    {
      id: "task-2",
      name: "Development",
      startTime: "01-09-2024",
      endTime: "01-20-2024",
      progress: 40,
      dependency: "task-1",
    },
  ];

  onTaskUpdate(event: TaskUpdateSuccessEventDetail) {
    console.log("Task updated:", event.taskId, event.updatedTask);
  }
}

The dates use inputDateFormat's default, 'MM-DD-YYYY'. For ISO dates, pass [options]="{ inputDateFormat: 'YYYY-MM-DD' }".

Module-based Component

import { Component } from "@angular/core";
import { TaskInput } from "ngx-apexgantt";

@Component({
  selector: "app-gantt",
  template: `
    <ngx-apexgantt [tasks]="tasks" [pixelsPerDay]="25.7" [height]="'500px'">
    </ngx-apexgantt>
  `,
})
export class GanttComponent {
  tasks: TaskInput[] = [
    // your tasks...
  ];
}
// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { NgxApexGanttModule } from "ngx-apexgantt";
import { GanttComponent } from "./gantt.component";

@NgModule({
  declarations: [GanttComponent],
  imports: [BrowserModule, NgxApexGanttModule],
})
export class AppModule {}

Component API

Inputs

Primary Configuration

InputTypeDescription
optionsAny ApexGantt option. GanttOptions is exported as an alias of GanttUserOptions for code written against earlier docs

Shortcut Inputs

Each shortcut sets one option. When options sets the same key, options wins, both at first render and on later changes.

InputTypeDescription
tasksTaskInput[]Array of tasks (shortcut for options.series)
pixelsPerDaynumberTimeline zoom in pixels-per-day; header tier auto-picks (0.5 year, 1.6 quarter, 4.9 month, 25.7 week, 80 day). Omit to auto-fit.
theme'light' | 'dark'Color theme
widthstring | numberChart width
heightstring | numberChart height

Styling Inputs

InputTypeDescription
classNamestringCSS class(es) added to the <ngx-apexgantt> element. Earlier releases accepted it but never applied it

There is no styles input; style the <ngx-apexgantt> element from your component's stylesheet.

Updating Inputs

After the chart has rendered, a changed input is applied with the chart's update(), sending only the option keys whose value changed. A theme toggle keeps the chart's tasks, zoom and scroll position. With apexgantt 3.20.0 a theme change returns the colours the theme owns (barBackgroundColor, rowBackgroundColors, fontColor and the rest listed under update()) to the new theme's values, except the ones you set in options, which keep your values. Every other option carries over. Earlier releases had no ngOnChanges: changing an input after the first render did nothing.

Inputs compare by reference, as everywhere in Angular. Replace an array or object to update it; a change made in place is not seen:

// seen: a new array
this.tasks = [...this.tasks, newTask];

// seen: a new options object; only enableTaskDrag is sent
this.options = { ...this.options, enableTaskDrag: false };

// NOT seen: same array, mutated in place
this.tasks.push(newTask);

Removing a key from options resets that option to its default (or to the shortcut input for it, when one is set).

With apexgantt 3.20.0, update() keeps the current task data: edits made by dragging, updateTask, addTask, deleteTask, moveTask and dependency changes survive any update that does not pass series or parsing. A new tasks array (or options.series) is sent as series, which replaces the data and clears the undo history, so only replace it when the data really changed.

Outputs (Events)

Every ApexGantt event has an Output of the same name, emitting the event's detail object. Earlier releases had seven of them (taskUpdate, taskUpdateSuccess, taskUpdateError, taskDragged, taskResized, selectionChange, dependencyArrowUpdate).

OutputDetail typeFires when
taskUpdateTaskUpdateEventDetailAn edit is being committed, before taskUpdateSuccess
taskUpdateSuccessTaskUpdateSuccessEventDetailAn edit was applied
taskUpdateErrorTaskUpdateErrorEventDetailAn edit failed to apply
taskValidationErrorTaskValidationErrorEventDetailThe task edit dialog failed validation
taskAddedTaskAddedEventDetailA task was inserted
taskDeletedTaskDeletedEventDetailA task was removed (once per task in a cascading delete)
taskMovedTaskMovedEventDetailA task was moved to a new parent
dependencyAddedDependencyChangeEventDetailA dependency link was created, by addDependency() or drawn by the user
dependencyRemovedDependencyChangeEventDetailA dependency link was removed, including once per link a deleted task takes with it
taskDraggedTaskDraggedEventDetailA bar was dropped at a new date
taskResizedTaskResizedEventDetailA bar was resized by one of its handles
taskProgressChangedTaskProgressChangedEventDetailThe in-bar progress handle was dragged to a new value
selectionChangeSelectionChangeEventDetailThe selected rows changed (needs enableSelection)
dependencyArrowUpdateDependencyArrowUpdateDetailInternal redraw signal when a linked task moves; use dependencyAdded / dependencyRemoved for link changes
historyChangeHistoryChangeEventDetailThe undo/redo stack changed
sortChangeSortChangeEventDetailThe active sort changed
filterChangeFilterChangeEventDetailThe active filter changed
groupChangeGroupChangeEventDetailThe active grouping changed
columnResizeColumnResizeEventDetailA task-list column was resized
columnReorderColumnReorderEventDetailTask-list columns were reordered

With apexgantt 3.20.0, taskUpdate and taskUpdateSuccess fire once per committed edit on every path (drag, resize, progress drag, dialog, inline edit, keyboard, updateTask), with a source field naming the path. To save edits, listen to taskUpdateSuccess alone: saving on taskDragged or taskResized as well saves the same edit twice. When a parent bar is dragged or moved with the keyboard, each moved task gets its own events, but all of the gesture's taskUpdate events arrive before its taskUpdateSuccess events.

The events are delivered through the Outputs only, once each. The underlying DOM events stop at the chart, so a listener on an element above <ngx-apexgantt> (or on document) does not receive them; bind to the Outputs instead. (Earlier releases could deliver an Output twice, because the bubbling DOM event of the same name also reached Angular's listener.)

Public Methods

Access these methods using @ViewChild:

import { Component, ViewChild } from "@angular/core";
import { NgxApexGanttComponent, TaskInput } from "ngx-apexgantt";

@Component({
  standalone: true,
  imports: [NgxApexGanttComponent],
  template: `<ngx-apexgantt #ganttChart [tasks]="tasks"></ngx-apexgantt>`,
})
export class MyComponent {
  @ViewChild("ganttChart") ganttChart!: NgxApexGanttComponent;

  tasks: TaskInput[] = [
    // your tasks...
  ];

  zoomIn() {
    this.ganttChart.zoomIn();
  }

  zoomOut() {
    this.ganttChart.zoomOut();
  }

  updateTask() {
    this.ganttChart.updateTask("task-1", { progress: 80 });
  }

  updateAll() {
    this.ganttChart.update({ pixelsPerDay: 4.9 });
  }

  undo() {
    // any ApexGantt method, through the instance
    this.ganttChart.getInstance()?.undo();
  }
}
MethodParametersDescription
getInstancenoneThe underlying ApexGantt instance, or null before the chart renders and after destroy(). Use it for every method not wrapped here
updateUpdate the chart with only the options passed; the rest keep their values
updateTaskUpdate a specific task. silent (apexgantt 3.20.0) skips taskUpdate and taskUpdateSuccess
zoomInnoneZoom in one step (the zoom is multiplied by 1.4)
zoomOutnoneZoom out one step
destroynoneDestroy the chart. The component also does this on ngOnDestroy

getInstance() is new in the release built for apexgantt 3.20.0 (there was never a getGanttInstance method, and earlier releases had no way to reach the instance). Through it you get the full method list: scrollToTask, addTask, deleteTask, moveTask, undo, redo, sort, filter, getState, exportChart and the rest, plus getTask, getTasks, getDependencies, expandTask and collapseTask from apexgantt 3.20.0.

To save the chart after the user edited it, save getInstance()?.getTasks() together with getInstance()?.getDependencies() (both apexgantt 3.20.0). A task's dependency holds only the link it was loaded with, so links added or drawn later are only in getDependencies(), which also reflects undo and redo. See Saving links for reloading them.

Server-Side Rendering

The component can be server-rendered (ngx-apexgantt release built for apexgantt 3.20.0). On the server it renders its empty container and stops there: it creates no chart, adds no event listeners, the methods above do nothing and getInstance() returns null. The chart is created in the browser when the component renders or hydrates there.

Usage Examples

Basic Example with Zoom Controls

import { Component, ViewChild } from "@angular/core";
import { NgxApexGanttComponent, TaskInput } from "ngx-apexgantt";

@Component({
  selector: "app-gantt-basic",
  standalone: true,
  imports: [NgxApexGanttComponent],
  template: `
    <div class="controls">
      <button (click)="ganttChart.zoomIn()">Zoom In</button>
      <button (click)="ganttChart.zoomOut()">Zoom Out</button>
    </div>
    <ngx-apexgantt
      #ganttChart
      [tasks]="tasks"
      [pixelsPerDay]="25.7"
      [height]="'500px'"
    >
    </ngx-apexgantt>
  `,
})
export class GanttBasicComponent {
  @ViewChild("ganttChart") ganttChart!: NgxApexGanttComponent;

  tasks: TaskInput[] = [
    {
      id: "task-1",
      name: "Planning",
      startTime: "01-01-2024",
      endTime: "01-10-2024",
      progress: 100,
    },
    {
      id: "task-2",
      name: "Development",
      startTime: "01-11-2024",
      endTime: "01-25-2024",
      progress: 60,
      dependency: "task-1",
    },
  ];
}

Advanced Example with Full Options

The zoom select uses ngModel, so the component imports FormsModule, and [ngValue] keeps the values numbers.

import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
import {
  NgxApexGanttComponent,
  GanttUserOptions,
  TaskUpdateSuccessEventDetail,
  TaskDraggedEventDetail,
} from "ngx-apexgantt";

@Component({
  selector: "app-gantt-advanced",
  standalone: true,
  imports: [NgxApexGanttComponent, FormsModule],
  template: `
    <div class="controls">
      <select [(ngModel)]="currentPixelsPerDay">
        <option [ngValue]="80">Day</option>
        <option [ngValue]="25.7">Week</option>
        <option [ngValue]="4.9">Month</option>
      </select>
      <button (click)="toggleTheme()">Toggle Theme</button>
    </div>
    <ngx-apexgantt
      [options]="ganttOptions"
      [pixelsPerDay]="currentPixelsPerDay"
      [theme]="currentTheme"
      (taskUpdateSuccess)="onTaskUpdate($event)"
      (taskDragged)="onTaskDragged($event)"
    >
    </ngx-apexgantt>
  `,
})
export class GanttAdvancedComponent {
  currentPixelsPerDay = 25.7;
  currentTheme: "light" | "dark" = "light";

  // ganttOptions does not set pixelsPerDay or theme, so the shortcut
  // inputs control them; changing either sends only that key to update()
  ganttOptions: GanttUserOptions = {
    series: [
      {
        id: "task-1",
        name: "Design Phase",
        startTime: "01-01-2024",
        endTime: "01-15-2024",
        progress: 75,
      },
      // more tasks...
    ],
    height: "600px",
    enableTaskDrag: true,
    enableTaskResize: true,
    rowHeight: 32,
  };

  toggleTheme() {
    this.currentTheme = this.currentTheme === "light" ? "dark" : "light";
  }

  onTaskUpdate(event: TaskUpdateSuccessEventDetail) {
    console.log("Task updated:", event.updatedTask);
    // save to backend
  }

  onTaskDragged(event: TaskDraggedEventDetail) {
    console.log("Task dragged:", event.taskId, event.daysMoved);
  }
}

The toolbar is always shown; there is no enableToolbar option. Use enableExport, enableTaskCRUDToolbar, enableQuickFilter and toolbarItems to choose what it holds.

Data Parsing Example

import { Component } from "@angular/core";
import { NgxApexGanttComponent, GanttUserOptions } from "ngx-apexgantt";

@Component({
  selector: "app-gantt-parsing",
  standalone: true,
  imports: [NgxApexGanttComponent],
  template: `<ngx-apexgantt [options]="ganttOptions"></ngx-apexgantt>`,
})
export class GanttParsingComponent {
  // external data format from API
  apiData = [
    {
      task_id: "T1",
      task_name: "Design Phase",
      start_date: "01-01-2024",
      end_date: "01-15-2024",
      completion: 75,
    },
    {
      task_id: "T2",
      task_name: "Development",
      start_date: "01-16-2024",
      end_date: "01-30-2024",
      completion: 40,
    },
  ];

  ganttOptions: GanttUserOptions = {
    series: this.apiData,
    parsing: {
      id: "task_id",
      name: "task_name",
      startTime: "start_date",
      endTime: "end_date",
      progress: "completion",
    },
    height: "500px",
  };
}

Event Handling Example

import { Component } from "@angular/core";
import {
  NgxApexGanttComponent,
  TaskInput,
  Task,
  TaskUpdateSuccessEventDetail,
  TaskDraggedEventDetail,
  TaskResizedEventDetail,
  TaskUpdateErrorEventDetail,
} from "ngx-apexgantt";

@Component({
  selector: "app-gantt-events",
  standalone: true,
  imports: [NgxApexGanttComponent],
  template: `
    <ngx-apexgantt
      [tasks]="tasks"
      (taskUpdateSuccess)="handleTaskUpdate($event)"
      (taskDragged)="handleTaskDragged($event)"
      (taskResized)="handleTaskResized($event)"
      (taskUpdateError)="handleError($event)"
    >
    </ngx-apexgantt>
    <div class="event-log">
      <h3>Event Log:</h3>
      <pre>{{ eventLog }}</pre>
    </div>
  `,
})
export class GanttEventsComponent {
  tasks: TaskInput[] = [
    // your tasks...
  ];

  eventLog = "";

  handleTaskUpdate(event: TaskUpdateSuccessEventDetail) {
    // with apexgantt 3.20.0 this fires for every edit path; event.source names it
    this.eventLog += `Task Updated: ${event.taskId}\n`;
    // sync with backend
    this.syncWithBackend(event.updatedTask);
  }

  handleTaskDragged(event: TaskDraggedEventDetail) {
    this.eventLog += `Task Dragged: ${event.taskId} moved ${event.daysMoved} days\n`;
  }

  handleTaskResized(event: TaskResizedEventDetail) {
    this.eventLog += `Task Resized: ${event.taskId} duration changed by ${event.durationChange}\n`;
  }

  handleError(event: TaskUpdateErrorEventDetail) {
    console.error("Update failed:", event.error);
  }

  async syncWithBackend(task: Task) {
    // your API call here
  }
}

This example logs drags and resizes for display and saves on taskUpdateSuccess only, which (with apexgantt 3.20.0) also fires for those drags and resizes.

Column Configuration

Customize task table column widths:

import { ColumnKey, GanttUserOptions } from "ngx-apexgantt";

ganttOptions: GanttUserOptions = {
  series: tasks,
  columnConfig: [
    {
      key: ColumnKey.Name,
      title: "Task Name",
      minWidth: "100px",
      flexGrow: 3,
    },
    {
      key: ColumnKey.StartTime,
      title: "Start",
      minWidth: "100px",
      flexGrow: 1.5,
    },
    {
      key: ColumnKey.Duration,
      title: "Duration",
      minWidth: "80px",
      flexGrow: 1,
    },
  ],
};

Annotations

Add visual annotations to highlight specific dates or date ranges. Annotation dates use inputDateFormat, like task dates:

ganttOptions: GanttUserOptions = {
  series: tasks,
  annotations: [
    {
      x1: "10-25-2024",
      x2: "11-08-2024",
      label: {
        text: "Sprint 1",
        fontColor: "#333333",
      },
    },
  ],
};

TypeScript Support

The library is fully typed. Import types as needed:

import {
  NgxApexGanttComponent,
  GanttUserOptions,
  GanttOptions, // alias of GanttUserOptions
  TaskInput,
  ColumnKey,
  TaskUpdateEventDetail,
  TaskDraggedEventDetail,
} from "ngx-apexgantt";

The detail type of every event is exported under the core's name (TaskUpdateEventDetail, HistoryChangeEventDetail, and so on); there are no TaskUpdateDetail or TaskDraggedDetail types. The GanttOptions alias is exported from the release built for apexgantt 3.20.0; on earlier releases import GanttUserOptions.