DHTMLX Gantt Chart Usage with Angular 2 Framework

| Comments (18)

We’ve already described how to use dhtmlxGantt with AngularJS 1.x. Since Angular 2 doesn’t have backward compatibility with Angular 1, such approach won’t work. That’s why our plan for today is to give you a good starting point for using dhtmlxGantt with Angular 2.x and recently released Angular 4.

Now we’re going to implement an angular gantt chart component for dhtmlxGantt (JavaScript Gantt Chart), add it into the small application and bind it to the RESTful API on a backend. We’ll write this demo in TypeScript since it’s recommended way, although it also can be done in plain JavaScript or in ES6.

Please mind that in this tutorial we won’t cover server-side logic of data saving. Instead, we’ll emulate the backend using Angular in-memory-web-api tool.

You can download a complete Angular 2 Gantt chart demo from GitHub

Preparing App

To set up development environment, we’ll use angular cli. If you don’t have angular cli you can get it with package manager (npm install -g @angular/cli) or check its documentation for more details. To create app skeleton, run following command:

    ng new gantt-angular2

After operation finish, we can go to app directory and run application:

    cd gantt-angular2
    ng serve

Now, if we open http://127.0.0.1:4200 we should see initial page. ng serve will watch the source file changing and rebuild app if necessary.

angular app

Now, open src/app and create folders for components, models and services to use them for necessary elements we’ll need further. So, we will put all the components to the components folder, all the models to the models folder and all the services and service helpers to the service folder.

Creating gantt chart component

At first, we should get DHTMLX Gantt chart code. Also we’ll want to get type declarations for gantt API. For this purpose, run the following commands:

npm install dhtmlx-gantt --save
npm install @types/dhtmlxgantt --save

To add dhtmlxGantt to AngularJS app we should create a new component. For this, create gantt.component.ts file with the following code:

import {Component, ElementRef, OnInit, ViewChild} from "@angular/core";

import "dhtmlx-gantt";
import {} from "@types/dhtmlxgantt";

@Component({
    selector: "gantt",
    styles: [
        `
        :host{
            display: block;
            height: 600px;
            position: relative;
            width: 100%;
        }
    `],
    template: "<div #gantt_here style='width: 100%; height: 100%;'></div>",
})
export class GanttComponent implements OnInit {
    @ViewChild("gantt_here") ganttContainer: ElementRef;

    ngOnInit(){
        gantt.init(this.ganttContainer.nativeElement);
    }
}

Here we’ve defined our new component. It can be used with “gantt” tag in html. When element is loaded, it initializes gantt inside container.

We should also add gantt styles to the page. For this, open styles.css file and add the following line to it:

@import "../node_modules/dhtmlx-gantt/codebase/dhtmlxgantt.css";

We should also add our component to app.module declarations. Now, “app.module.ts” should contain code like following:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';

import {AppComponent} from './app.component';
import {GanttComponent} from "./components/gantt.component";

@NgModule({
    declarations: [
        AppComponent,
        GanttComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule {}

Next, we can put out new component to the page. For this, open “app.component.html” and put following code in there:

<gantt></gantt>

Now if we open the page, we should see an empty gantt chart on it.

empty gantt chart

Data Loading

To add data loading to our angular gantt chart, we are going to add task and link services. But let’s create models at first.

Create task.ts with following code:

export class Task {
    id: number;
    start_date: string;
    text: string;
    progress: number;
    duration: number;
    parent: number;
}

Create link.ts with following code:

export class Link {
    id: number;
    source: number;
    target: number;
    type: string;
}

Now, let’s create our services. Service is a class that responsible for doing a specific task. Angular services can be injected using Dependency Injection mechanism and include data, function or some features necessary for your application. We’re going to create a data service that we’ll use to provide our gantt chart with tasks.

Create task.service.ts with following code:

import {Injectable} from "@angular/core";
import {Task} from "../models/task";

@Injectable()
export class TaskService {
    get(): Promise<Task[]>{
        return Promise.resolve([
            {id: 1, text: "Task #1", start_date: "2017-04-15 00:00", duration: 3, progress: 0.6},
            {id: 2, text: "Task #2", start_date: "2017-04-18 00:00", duration: 3, progress: 0.4}
        ]);
    }
}

We’ve added @Injectable() decorator to our service. It marks a class as available to an injector for instantiation. We’ll inject it to our component further.

Currently it returns resolved promise with hardcoded data. But you can load data from the server side and return promise. We will discuss it a little closer below.

Now we need to add link service. Create link.service.ts with following code:

import {Injectable} from "@angular/core";
import {Link} from "../models/link";

@Injectable()
export class LinkService {
    get(): Promise<Link[]> {
        return Promise.resolve([
            {id: 1, source: 1, target: 2, type: "0"}
        ]);
    }
}

This service is pretty much the same as task service.

Now open our gantt.component.ts. We should add TaskService and LinkService to our component. At first, add necessary imports for our services:

import {TaskService} from "../services/task.service";
import {LinkService} from "../services/link.service";

Also we should add providers property to @Component decorator argument:

    providers: [TaskService, LinkService]

It tells angular to create fresh instance of our services when in creates new GanttComponent. So, our component can use that services to get tasks and links.

Now, we can inject our services to the component. For this purpose, add the following constructor to GanttComponent class:

constructor(private taskService: TaskService, private linkService: LinkService){}

Put following code to ngOnInit function.

gantt.config.xml_date = "%Y-%m-%d %H:%i";

        gantt.init(this.ganttContainer.nativeElement);

        Promise.all([this.taskService.get(), this.linkService.get()])
            .then(([data, links]) => {
                gantt.parse({data, links});
            });

Here we’ve added xml_date config definition. It sets loading tasks data format. Also here we call our services to get function and then are waiting for response to put data to gantt. gantt.parse accepts data object of following structure.

We should get code like following at our gantt.component.ts

import {Component, ElementRef, OnInit, ViewChild} from "@angular/core";

import "dhtmlx-gantt";
import {TaskService} from "../services/task.service";
import {LinkService} from "../services/link.service";

import {} from "@types/dhtmlxgantt";

@Component({
    selector: "gantt",
    styles: [
        `
        :host{
            display: block;
            height: 600px;
            position: relative;
            width: 100%;
        }
    `],
    providers: [TaskService, LinkService],
    template: "<div #gantt_here style='width: 100%; height: 100%;'></div>",
})
export class GanttComponent implements OnInit {
    @ViewChild("gantt_here") ganttContainer: ElementRef;

    constructor(private taskService: TaskService, private linkService: LinkService){}

    ngOnInit(){
        gantt.config.xml_date = "%Y-%m-%d %H:%i";

        gantt.init(this.ganttContainer.nativeElement);

        Promise.all([this.taskService.get(), this.linkService.get()])
            .then(([data, links]) => {
                gantt.parse({data, links});
            });
    }
}

Now, if we open app, we should see our tasks with link between them.

gantt with tasks

Data Saving

In this tutorial we’re not going to create an actual database and write a backend. Instead, we’ll mock backend with a help of angular-in-memory-web-api library – it’ll intercept xhr requests and call data storage we’ll define on the client. If it’s not what you want, you can skip the first step and the app will call the real backend.

1. At first, we should install angular-in-memory-web-api. For this, run the following command:

npm install angular-in-memory-web-api --save

Open app.module.ts to add InMemoryWebApiModule and define our mock database initialization. We will create necessary class on next step. For now, our app.module.ts should look like following:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';

import {InMemoryWebApiModule} from 'angular-in-memory-web-api';
import {InMemoryDataService}  from './services/in-memory-data.service'

import {AppComponent} from './app.component';
import {GanttComponent} from "./components/gantt.component";

@NgModule({
  declarations: [
     AppComponent,
     GanttComponent
  ],
  imports: [
     BrowserModule,
     FormsModule,
     HttpModule,
     InMemoryWebApiModule.forRoot(InMemoryDataService)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Create service in-memory-data.service.ts with the following code:

import {InMemoryDbService} from "angular-in-memory-web-api";

export class InMemoryDataService implements InMemoryDbService {
    createDb() {
        let    tasks = [
            {id: 1, text: "Task #1", start_date: "2017-04-15 00:00", duration: 3, progress: 0.6},
            {id: 2, text: "Task #2", start_date: "2017-04-18 00:00", duration: 3, progress: 0.4}
        ];
        let links = [
            {id: 1, source: 1, target: 2, type: "0"}
        ];
        return {tasks, links};
    }
}

2. Let’s create a helper for our requests. Do so, create service-helper.ts with the following code:

import {Response} from "@angular/http";

export function ExtractData(res: Response): any {
    let body = res.json();
    return body  && body.data ? body.data: {};
}

export function HandleError(error: any): Promise<any>{
    console.log(error);
    return Promise.reject(error);
}

ExtractData function will help us to get data from server response. HandleError will put error to console in case of some errors.

3. Update our services to handle adding, updating and deleting items. Open task.service.ts and put the following code to it:

import {Injectable} from "@angular/core";
import {Task} from "../models/task";
import {Http} from "@angular/http";
import {ExtractData, HandleError} from "./service-helper";

import 'rxjs/add/operator/toPromise';

@Injectable()
export class TaskService {
    private taskUrl = "api/tasks";

    constructor(private http: Http) {}

    get(): Promise<Task[]>{
        return this.http.get(this.taskUrl)
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }

    insert(task: Task): Promise<Task> {
        return this.http.post(this.taskUrl, JSON.stringify(task))
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }


    update(task: Task): Promise<void> {
        return this.http
            .put('${this.taskUrl}/${task.id}', JSON.stringify(task))
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }

    remove(id: number): Promise<void> {
        return this.http.delete('${this.taskUrl}/${id}')
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }
}

We’ve defined taskUrl as a private element of our service. It contains url to our REST API. In order to send HTTP requests, we’ve injected HTTP class to our service.

To insert a new item, we send POST request to our url with new task in body. To update item, we send PUT request to url/item_id. This request also contains updated task in body. To remove item, we send delete request to url/item_id. In this case, an item with such id will be removed.

Now, open link.service.ts and update our LinkService.

import {Injectable} from "@angular/core";
import {Link} from "../models/link";
import {Http} from "@angular/http";
import {ExtractData, HandleError} from "./service-helper";

import 'rxjs/add/operator/toPromise';

@Injectable()
export class LinkService {
    private linkUrl = "api/links";

    constructor(private http: Http) {}

    get(): Promise<Link[]> {
        return this.http.get(this.linkUrl)
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }

    insert(link: Link): Promise<Link> {
        return this.http.post(this.linkUrl, JSON.stringify(link))
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }

    update(link: Link): Promise<void> {
        return this.http.put('${this.linkUrl}/${link.id}', JSON.stringify(link))
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }

    remove(id: number): Promise<void> {
        return this.http.delete('${this.linkUrl}/${id}')
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }
}

It’s pretty much the same as TaskService and provides same API for links.

Now, open gantt.component.ts and add tasks editing logic to it.

import {Component, ElementRef, OnInit, ViewChild} from "@angular/core";
import {TaskService} from "../services/task.service";
import {LinkService} from "../services/link.service";
import {Task} from "../models/Task";
import {Link} from "../models/Link";

import "dhtmlx-gantt";

declare let gantt: any;

@Component({
    selector: "gantt",
    styles: [
        '
        :host{
            display: block;
            height: 600px;
            position: relative;
            width: 100%;
        }
    '
],
    providers: [TaskService, LinkService],
    template: "<div #gantt_here style='width: 100%; height: 100%;'></div>",
})
export class GanttComponent implements OnInit {
    @ViewChild("gantt_here") ganttContainer: ElementRef;

    constructor(private taskService: TaskService, private linkService: LinkService){}

    ngOnInit(){
        gantt.config.xml_date = "%Y-%m-%d %H:%i";

        gantt.init(this.ganttContainer.nativeElement);

        gantt.attachEvent("onAfterTaskAdd", (id, item) => {
            this.taskService.insert(this.serializeTask(item, true))
                .then((response)=> {
                    if (response.id != id) {
                        gantt.changeTaskId(id, response.id);
                    }
                });
        });

        gantt.attachEvent("onAfterTaskUpdate", (id, item) => {
            this.taskService.update(this.serializeTask(item));
        });

        gantt.attachEvent("onAfterTaskDelete", (id) => {
            this.taskService.remove(id);
        });

        gantt.attachEvent("onAfterLinkAdd", (id, item) => {
            this.linkService.insert(this.serializeLink(item, true))
                .then((response) => {
                    if(response.id != id){
                        gantt.changeLinkId(id, response.id);
                    }
                });
        });

        gantt.attachEvent("onAfterLinkUpdate", (id, item) => {
            this.linkService.update(this.serializeLink(item));
        });

        gantt.attachEvent("onAfterLinkDelete", (id) => {
            this.linkService.remove(id);
        });

        Promise.all([this.taskService.get(), this.linkService.get()])
            .then(([data, links]) => {
                gantt.parse({data, links});
            });
    }

    private serializeTask(data: any, insert: boolean = false): Task {
        return this.serializeItem(data, insert) as Task;
    }

    private serializeLink(data: any, insert: boolean = false): Link {
        return this.serializeItem(data, insert) as Link;
    }

    private serializeItem(data: any, insert: boolean): any{
        var result = {};

        for (let i in data) {
            if (i.charAt(0) == "$" || i.charAt(0) == "_") continue;
            if(insert && i == "id") continue;
            if (data[i] instanceof Date) {
                result[i] = gantt.templates.xml_format(data[i]);
            }
            else {
                result[i] = data[i];
            }
        }

        return result;
    }
}

Here we attached several event handlers to the gantt object. They’ll capture adding, updating and deleting of tasks and links in gantt chart. Inside the event handlers, we call services that’ll save changes to the backend. Also, we must consider that after we insert tasks and links into the database, they obtain database ids – when it happens we’ll update gantt with new id using gantt.changeTaskId or gantt.changeLinkId methods.

After that, we get a fully functional Angular component for gantt chart which can use RESTful API for CRUD operations.

Keep in mind that in this demo we’ve used angular-in-memory-api library to emulate the data storage, but in real life you’ll most probably want to save changes to the real database. In order to do that you’ll either need to remove in-memory-web-api from the app, or configure it to pass requests thru to the real backend, and implement a data storage.

Once again, you are welcome to check a complete demo of our gantt chart and Angular 2 on GitHub.

Which technologies/frameworks are you using?

We need your feedback to provide the right integrations at the right time. Please leave your requests here:

Thank you in advance and stay tuned for new tutorials!

Comments

  1. Robert March 24, 2017 at 9:08 pm

    In the tutorial you are using “declare let gantt: any;”
    Why are you not using the typings for the Gantt?

    https://www.npmjs.com/package/@types/dhtmlxgantt

    • Aras Kairys (DHTMLX team) March 27, 2017 at 2:17 pm

      Hi Robert,
      Thanks for noticing, that’s a valid point.
      Seems like we’ve overlooked it – we’ll review the tutorial and most probably will publish an update in the nearest time.

    • Maria (DHTMLX team) March 27, 2017 at 4:59 pm

      The tutorial is updated. Robert, thank you again.

  2. Vojtech March 28, 2017 at 1:17 am

    Hi, in the title you are talking about AngularJS 2.0 which right now doesn’t exist (still in some 1.6.x version). Later in the article, you are using and referring to the angular2 (angular).

    • Maria (DHTMLX team) March 28, 2017 at 2:22 pm

      Thanks for the comment, Vojtech!
      When writing AngularJS 2, we meant Angular 2. I’m sorry if this results in confusion, we’ve already changed to Angular 2 to avoid further misunderstanding.

  3. Solomon Thuo March 29, 2017 at 9:31 am

    Nice example. Thank you. Am keen to know when we an expect to have angular 2 integration with dhtmlx. Thanks. Is it possible to use angular 2 with dhtmlx as of now. Am new to both frameworks but interested in working with both.

    • Aras Kairys (DHTMLX team) March 29, 2017 at 3:19 pm

      Hi Solomon,
      Thank you for your interest to our products.
      Yes, DHTMLX products and be used with Angular 2. We have only the tutorial about such integration for dhtmlxGantt at the moment, but the same principles can be used to integrate other DHTMLX products with Angular 2.

  4. Miguel April 12, 2017 at 4:52 pm

    Hi! Great job with Angular 2 integration.

    I implemented my own component using the information in this article. I’m trying now to add a “today vertical marker” into the gantt, but I’m unable to do that. Although I imported the typings, and the function is typed in index.d.ts(902), the gantt component stills says “gantt.addMarker is not a function”. I explored the dhtmlxgantt.js and I didn’t found any reference to addMarker function, so I think the Angular 2 component hasn’t implemented addMarker function.

    Can you tell me something about that?

    Thank you in advance.

    • Maxim (DHTMLX team) April 13, 2017 at 7:38 pm

      Hello.
      To use “addMarker” function you should also add “dhtmlxgantt_marker” extension with dhtmlx-gantt.
      You should get something like following:
      import “dhtmlx-gantt”;
      import “dhtmlx-gantt/codebase/ext/dhtmlxgantt_marker”

      See article: https://docs.dhtmlx.com/gantt/desktop__markers.html

  5. singaravelu s April 19, 2017 at 7:44 pm

    hi, we are considering to use DHTMLX in our Single Page Application using Angular JS 4, is there any wrappers you are creating for Angular JS 4. We would be really looking forward to use your full suite.

    • Aras Kairys (DHTMLX team) April 27, 2017 at 8:03 pm

      Hello,
      Unfortunately, we don’t have any wrappers for Angular JS 4 at the moment.
      Nevertheless, dhtmlxGantt (as well as other DHTMLX products) are fully client-side solutions, so the integration with Angular JS 4 is possible through some coding.

  6. Pratik Sahu April 21, 2017 at 10:19 pm

    I tried downloading and running it everything works fine except that it gives error that ‘cannot get /api/tasks’ and ‘cannot get /api/url’. Please can u resolve it.
    Further I want to use real backend (firebase), can you guide me as in what steps i should perform..
    Thank You

    • Aras Kairys (DHTMLX team) April 27, 2017 at 8:16 pm

      Hi Pratik,
      Everything works fine for us. If the error persists, please create a topic on our forum: https://forum.dhtmlx.com/
      This example uses a memory web api, so the requests aren’t really sent to the server-side, and the replies are emulated on the client-side. You should remove this api in the real app.

  7. Cyril June 27, 2017 at 5:50 pm

    Thanks for the article! We are using Angular and were thinking about aquiring the dhtmlx components; I’m happy that the two technologies can coexist.

  8. Erik August 21, 2017 at 6:13 am

    Any plans on a tutorial to integrating the DHTMLX Scheduler with Angular 2? It’s been difficult trying to get it to work testing on Angular 2.

    • Aras Kairys (DHTMLX team) August 21, 2017 at 2:37 pm

      Hi Erik,
      Yes, we plan to create such a tutorial for dhtmlxScheduler.
      Stay tuned for the latest news in our blog.

    • Aras Kairys (DHTMLX team) September 27, 2017 at 6:41 pm

      Hi Erik,
      We are glad to inform you that the tutorial about the integration of dhtmlxScheduler and Angular released: https://dhtmlx.com/blog/angular-dhtmlxscheduler-tutorial/

  9. Juanitabrear November 17, 2017 at 11:43 pm

    I be blaren.geneeskrachtige.nl to discern you close last the thump spot in this community, principles conceive of how much intelligence is here. History clumsy and proven facts.

Leave a Reply