跳到主要内容

Data Binding & State Management in Angular Gantt

Angular Gantt supports two data ownership models:

  1. Angular state/store as source of truth (recommended for most applications).
  2. 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 tasks and links,
  • the wrapper receives arrays through inputs,
  • chart changes are captured via data.save or data.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 instance while still pushing stale tasks/links arrays from Angular state,
  • ignoring data.save / data.batchSave and 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 -> tasks
  • link / links -> links
  • resource / resources -> resources
  • assignment / resourceAssignment / resourceAssignments -> resourceAssignments

Queue behavior summary:

  • small debounce-based batching,
  • create + update coalesced into one create with the latest data,
  • create + delete removed,
  • internal !nativeeditor_status stripped 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.

Use an injectable service with a BehaviorSubject (or similar) to hold tasks, links, and UI state.

This is the pattern used in the Angular public sample and documented in Using Angular Gantt with RxJS.

Loading from an API into Angular state

Typical flow:

  1. Fetch data in a service or route resolver.
  2. Normalize or map date formats if needed.
  3. Push data into your store/component state.
  4. Pass the arrays to <dhx-gantt>.
  5. Handle edits with data.save or data.batchSave and persist to backend.

Use this when Angular state is your source of truth and the backend is still the long-term persistent source.

Gantt As Source Of Truth In An Angular App

When this model makes sense

Choose it when the page is mostly the chart and the surrounding Angular UI does not need to react to every task/link update.

Providing initial data

You can initialize Gantt-managed data with any of these patterns:

  • data.load URL
  • data.load function (sync or async)
  • initial tasks/links arrays, then stop treating them as live source-of-truth inputs

How updates work

The Gantt instance applies user changes internally and sends them through save or batchSave.

Angular does not need to reassign tasks/links after each change unless you explicitly want to mirror them.

ID Remapping And Backend Responsibility

Create actions often begin with temporary client-side IDs.

  • In save mode, backend responses should return persistent IDs so Gantt can remap internal records.
  • In batchSave mode, there is no per-item return path, so ID remapping must be handled explicitly in your persistence workflow if the backend assigns new IDs.

Backend remains responsible for:

  • validation,
  • permission checks,
  • persistent ID assignment,
  • consistent response payloads.

Continue With

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.