Data Binding & State Management in Angular Gantt
Angular Gantt supports two data ownership models:
- Angular state/store as source of truth (recommended for most applications).
- Gantt as source of truth (performance-focused for specialized pages).
Choose one model per page/feature area and keep it consistent.
Angular State Or Store As Source Of Truth
In this model:
- your component state or RxJS store owns
tasksandlinks, - the wrapper receives arrays through inputs,
- chart changes are captured via
data.saveordata.batchSave, - callbacks update your state/store and new arrays flow back into
<dhx-gantt>.
Best for
- Angular pages with toolbars/forms that must stay in sync with the chart,
- team codebases already built around services and RxJS,
- predictable state transitions and easier debugging.
Tradeoffs
- more application-state updates for heavy chart operations,
- more frequent synchronization work during bulk edits.
Anti-patterns to avoid
- mutating data through
instancewhile still pushing staletasks/linksarrays from Angular state, - ignoring
data.save/data.batchSaveand expecting chart edits to persist in your app state automatically.
Full-flow example (component state)
import { Component } from '@angular/core';
import {
DhxGanttComponent,
type AngularGanttDataConfig,
type SerializedTask,
type SerializedLink,
} from '@dhtmlx/trial-angular-gantt';
@Component({
standalone: true,
imports: [DhxGanttComponent],
template: `<dhx-gantt [tasks]="tasks" [links]="links" [data]="dataConfig"></dhx-gantt>`,
})
export class GanttPageComponent {
tasks: SerializedTask[] = [];
links: SerializedLink[] = [];
dataConfig: AngularGanttDataConfig = {
save: (entity, action, item, id) => {
if (entity === 'task') {
if (action === 'create') this.tasks = [...this.tasks, item];
if (action === 'update') this.tasks = this.tasks.map((t) => String(t.id) === String(id) ? { ...t, ...item } : t);
if (action === 'delete') this.tasks = this.tasks.filter((t) => String(t.id) !== String(id));
}
if (entity === 'link') {
if (action === 'create') this.links = [...this.links, item];
if (action === 'update') this.links = this.links.map((l) => String(l.id) === String(id) ? { ...l, ...item } : l);
if (action === 'delete') this.links = this.links.filter((l) => String(l.id) !== String(id));
}
},
};
}
Gantt As Source Of Truth
In this model, the chart and backend own most of the runtime data lifecycle.
Best for
- very large datasets,
- chart-centric screens,
- heavy auto-scheduling or chained edits where frequent app-store updates are expensive.
Tradeoffs
- less immediate visibility of live chart state in Angular services/components,
- extra discipline required when mixing occasional input updates with imperative operations.
Anti-patterns to avoid
- partial mirroring without a clear reconciliation plan,
- refeeding stale server snapshots after users already changed data in the chart.
Server transport example
dataConfig = {
load: '/api/gantt/load',
save: async (entity: string, action: string, payload: any, id: string | number) => {
const response = await fetch(`/api/gantt/${entity}`, {
method: action === 'delete' ? 'DELETE' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, payload, id }),
});
return await response.json();
},
};
Use this when your backend is the authoritative state owner and Angular does not need to mirror every edit in real time.
Callback Contracts
data.save
save is passed to gantt.createDataProcessor(save) and receives per-change payloads.
Typical function shape:
(entity: string, action: string, data: any, id: string | number) => any
Use this when changes are mostly singular and easy to apply one-by-one.
data.batchSave
batchSave receives grouped payloads:
interface BatchChanges {
tasks?: DataCallbackChange[];
links?: DataCallbackChange[];
resources?: DataCallbackChange[];
resourceAssignments?: DataCallbackChange[];
}
Entity-to-bucket mapping includes:
task/tasks->taskslink/links->linksresource/resources->resourcesassignment/resourceAssignment/resourceAssignments->resourceAssignments
Queue behavior summary:
- small debounce-based batching,
create+updatecoalesced into onecreatewith the latest data,create+deleteremoved,- internal
!nativeeditor_statusstripped from payloads.
Use this when one chart action can trigger many downstream changes.
Loading Data Into Angular State
Local component state
Use local component fields for small pages or prototypes.
Load data in Angular, then assign arrays to tasks and links inputs. Keep callback handlers in the same component.