DHTMLX Scheduler Usage with Angular Framework

| Comments (16)

Angular is one of the most popular frameworks for building single-page mobile and web applications. In one of the previous tutorials, you’ve seen that you can embed DHTMLX Gantt in Angular apps. You can also embed DHTMLX Scheduler.

Check a complete demo of dhtmlxScheduler and Angular integration on GitHub

To be able to follow along with the tutorial, you should have node.js installed. So if you don’t have it, go ahead and get it. Besides, you’ll need Angular CLI to set up the environment, you can get it with the package manager (npm install -g @angular/cli). For more info, you can check Angular documentation.

Basic App Structure

Now that the starting ground is ready, you can begin setting up the environment. Let’s use Angular CLI and create the basic structure of the app. Run the following command:

ng new scheduler-angular

This is quite time-consuming, because even the simplest Angular app requires a lot of files and configuration. After the operation has finished, you can go to the app directory and run the application:

cd scheduler-angular
ng serve --open

When the app is ready, a new browser tab will open, and you’ll see the start page. This happens automatically due to the –open parameter. Without it, you would have to open http://127.0.0.1:4200 by yourself. ng serve will also watch the source file and rebuild the app if any changes have been made.

Angular 2 dhtmlxScheduler Tutorial creating basic app structure

Later in the tutorial, you are going to create new components, models, and services for the app. So you need to create folders for them in src/app with the same names.

Creating Scheduler Component

To be able to embed Scheduler into the app, you should get DHTMLX Scheduler code and the type declaration for the Scheduler API. Run the following commands:

npm install dhtmlx-scheduler --save
npm install @types/dhtmlxscheduler --save

To add dhtmlxScheduler to an Angular app, you should also create a new component. Let’s store the code for the scheduler in three files. The first file, scheduler.component.html, will contain the template for the scheduler. Let’s create the file in the components folder:

<div #scheduler_here class="dhx_cal_container" style="width: 100%; height:100vh">
   <div class="dhx_cal_navline">
       <div class="dhx_cal_prev_button">&nbsp;</div>
       <div class="dhx_cal_next_button">&nbsp;</div>
       <div class="dhx_cal_today_button"></div>
       <div class="dhx_cal_date"></div>
       <div class="dhx_cal_tab" name="day_tab"></div>
       <div class="dhx_cal_tab" name="week_tab"></div>
       <div class="dhx_cal_tab" name="month_tab"></div>
   </div>
   <div class="dhx_cal_header"></div>
   <div class="dhx_cal_data"></div>
</div>

The styles for the scheduler will also be kept in a separate stylesheet, create it under the name scheduler.component.css. It will contain the size parameters of the container:

:host{
   display: block;
   height: 100%;
   position: relative;
   width: 100%;
}

And the scheduler stylesheet:

@import '~dhtmlx-scheduler/codebase/dhtmlxscheduler.css';

After that, create the scheduler.component.ts file in the same folder. First, import the necessary modules:

import {Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from "@angular/core";
import "dhtmlx-scheduler";
import {} from "@types/dhtmlxscheduler";

The last one, ViewEncapsulation, is needed to correctly apply the styles for the scheduler. By default, Angular encapsulates styles of components by adding unique attributes to their CSS and HTML. As the scheduler HTML template is created dynamically, using the default technique is impossible. The alternatives are to enable the native Shadow DOM view encapsulation or to disable it at all. Since the Shadow DOM isn’t supported everywhere, we’ll choose to disable encapsulation.

Let’s define the scheduler component, include the styles and the template that we’ve created, and disable the Shadow DOM view encapsulation:

@Component({
    encapsulation: ViewEncapsulation.None,
    selector: "scheduler",
    styleUrls: ['scheduler.component.css'],
    templateUrl: 'scheduler.component.html'
})

Later on, the component can be added to the page by inserting the scheduler tag. When the element is loaded, the scheduler is initialized inside the container.

Now we need a way to initialize the dhtmlxScheduler once Angular places the component on the page. It can be done by implementing the OnInit interface:

export class SchedulerComponent implements OnInit {
    @ViewChild("scheduler_here") schedulerContainer: ElementRef;

    ngOnInit(){
        scheduler.init(this.schedulerContainer.nativeElement, new Date());
    }
}

Next, let’s add the component to the app.module declaration. Modify the code in app.module.ts. Import the scheduler component and two more Angular modules for HTTP requests and forms:

import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {SchedulerComponent} from "./components/scheduler.component";

And add them to NgModule declarations and imports:

@NgModule({
    declarations: [
        AppComponent,
        SchedulerComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule
    ],
    //unchanged code
})
//unchanged code

Now that the Scheduler module is created and recognized by Angular, you can add the new component to the page. Open app.component.html (located in src/app) and insert the scheduler tag in there:

<scheduler></scheduler>

Now if you open the page again, you will see an empty Scheduler on it.

Angular 2 dhtmlxScheduler Tutorial creating scheduler component

Providing Data

At this stage, an empty Scheduler is good to look at, but I bet you want more than that. Scheduler needs data. To add data loading to the Angular Scheduler, you need to add an event service. But before that, let’s define the event model. Create event.ts (in the models folder) and add the definition:

export class Event {
    id: string;
    start_date: string;
    end_date: string;
    text: string;
}

Now let’s create an event service. A service is a class that will be responsible for creating a specific event. Services in Angular can be injected by using the Dependency Injection mechanism. They can include data, functions or some features necessary for the application. You need to create a data service that will be used to provide the scheduler with events. Add the event.service.ts file and import the event model and the Injectable decorator:

import {Injectable} from "@angular/core";
import {Event} from "../models/event";

Next, add the @Injectable() decorator to the service. The decorator marks a class as available to an injector for instantiation. You’ll inject it into the component later in the tutorial.

@Injectable()
export class EventService {
    get(): Promise<Event[]>{
        return Promise.resolve([
            {id: 1, start_date: "2017-09-01 00:00", end_date: "2017-09-01 13:00", text: "Event 1"},
            {id: 2, start_date: "2017-09-03 00:00", end_date: "2017-09-03 13:00", text: "Event 2"},
        ]);
    }
}

Currently, the get method returns a resolved promise with hard coded data. However, you can load data from the server side and also return a promise. The issue will be discussed a little further below.

The scheduler component is supposed to use EventService to get events. To enable this, let’s add EventService to the component. First, import the necessary module for the service in scheduler.component.ts:

import {EventService} from "../services/event.service";

You should also specify EventService as a provider in the @Component decorator:

providers: [EventService]

Now every time a new SchedulerComponent is initialized, a fresh instance of the service will be created.

The service should be prepared to be injected into the component. For this purpose, add the following constructor to the SchedulerComponent class:

constructor(private eventService: EventService){}

Modify the ngOnInit function:

▸ it should set the data format for loading events (XML in this case),
▸ call the services to get the function and then wait for a response to put the data to the scheduler.

scheduler.config.xml_date = "%Y-%m-%d %H:%i";
scheduler.init(this.schedulerContainer.nativeElement);
this.eventService.get()
    .then((data) => {
         scheduler.parse(data, "json");
    });

scheduler.parse accepts a data object in JSON format.

You can check the complete code for the scheduler.components.ts file.

Now, if you reopen the app page, you should see a scheduler with events.

Angular 2 dhtmlxScheduler Tutorial data loading

Saving Data

Creating an actual database and writing a backend is beyond the issues in this tutorial. Instead, we’ll mock the backend with the help of angular-in-memory-web-api library. It’ll catch an XMLHttpRequest and call the data storage defined on the client side. If you want the app to call the real backend, you can skip the first step.

1. Install angular-in-memory-web-api

First, you should install angular-in-memory-web-api. Run the following command:

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

To define the mock database initialization, open app.module.ts and add InMemorywebApiModule. The necessary class will be defined on the next step. For now, make the following changes to app.module.ts:

▸ add imports:

import {InMemorywebApiModule} from 'angular-in-memory-web-api';
import {InMemoryDataService}  from './services/in-memory-data.service'
import {AppComponent} from './app.component';
import {SchedulerComponent} from "./components/scheduler.component";

▸ add InMemorywebApiModule to NgModule declarations:

@NgModule({
  //unchanged code
  imports: [
     //other
     InMemorywebApiModule.forRoot(InMemoryDataService)
  ],
  //unchanged code
})
export class AppModule {}

Next, you need to add a service that will initialize a mock database with the same hard coded data. Create a service file in-memory-data.service.ts with the following code:

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

export class InMemoryDataService implements InMemoryDbService {
    createDb() {
        let events = [
            {id: 1, start_date: "2017-09-01 00:00", end_date: "2017-09-01 13:00", text: "Event 1"},
            {id: 2, start_date: "2017-09-03 00:00", end_date: "2017-09-03 13:00", text: "Event 2"}
        ];
    return {events};
    }
}

2. Helper for Requests

Let’s create a helper for the requests that will get the data from a server response and handle errors. Add the service-helper.ts file to services and import the necessary module:

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

Let’s define a function to help get data from a server response:

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

And finally, here’s a handler for errors that will notify users by sending error messages to console when something goes wrong:

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

3. Services for CRUD Operations

The services should handle adding, updating and deleting items in the scheduler. Open event.service.ts and import the helper and two more modules for HTTP requests and promises:

import {Http} from "@angular/http";
import {ExtractData, HandleError} from "./service-helper";
import 'rxjs/add/operator/toPromise';

Next, add a constructor to the EventService class:

@Injectable()
export class EventService {
    private eventUrl = "api/events";

    constructor(private http: Http) {}

    //get and other methods here
}

Update the get method so that it got the data from the server:

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

eventUrl is a private element of the service. It contains the URL to the REST API. In order to send HTTP requests, an HTTP class has been injected into the service.

Finally, add the insert, update and remove methods for operations with events in the scheduler. To insert a new item, you need to send a POST request to the URL with the new event in its body:

    insert(event: Event): Promise<Event> {
        return this.http.post(this.eventUrl, JSON.stringify(event))
            .toPromise()
            .then(ExtractData)
            .catch(HandleError);
    }

To update an item, you are to send a PUT request to url/item_id. This request also contains the updated event in its body.

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

To remove an item, you need to send a delete request to url/item_id.

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

Feel free to check out the complete code of event.service.ts on GitHub.

Great. Now, open scheduler.component.ts and add events for editing logic to it. First, import the event model:

import {Event} from "../models/Event";

Next, attach several event handlers to the scheduler object. They’ll catch adding, updating and deleting events in the scheduler. Inside the event handlers, services that’ll save changes to the backend will be called.

Before sending events to the service, we should preprocess them. It’s necessary to clean private properties that are assigned by the scheduler and shouldn’t be saved and also convert the dates to strings. Here’s a private method that will be called inside the event handlers:

private serializeEvent(data: any, insert: boolean = false): Event {
    const 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] = scheduler.templates.xml_format(data[i]);
        } else {
            result[i] = data[i];
        }
    }
    return result as Event;
}

And here is the Scheduler component with event handlers:

@Component({
    //unchanged code
})

export class SchedulerComponent implements OnInit {
    @ViewChild("scheduler_here") schedulerContainer: ElementRef;

    constructor(private eventService: EventService){}

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

        scheduler.init(this.schedulerContainer.nativeElement);

        scheduler.attachEvent("onEventAdded", (id, ev) => {
            this.eventService.insert(this.serializeEvent(ev, true))
                .then((response)=> {
                    if (response.id != id) {
                        scheduler.changeEventId(id, response.id);
                    }
                })
        });

        scheduler.attachEvent("onEventChanged", (id, ev) => {
            this.eventService.update(this.serializeEvent(ev));
        });

        scheduler.attachEvent("onEventDeleted", (id) => {
            this.eventService.remove(id);
        });

       this.eventService.get()
            .then((data) => {
                scheduler.parse(data, "json");
            })
    }

    //serializing events method
    private serializeEvent(data: any, insert: boolean = false): Event {/* */}
}

Besides, you must remember that after you insert events into the database, they obtain database IDs. When it happens, you’ll update scheduler with the new ID using the scheduler.changeEventId method.

Well, Angular Scheduler is ready, you are welcome to check out the source code. The app can use RESTful API for CRUD operations. Keep in mind that in this demo angular-in-memory-api library was used to emulate the data storage. However, in real life apps, you’ll probably want to save changes to a real database. There are two solutions. First, you can remove in-memory-web-api from the app (in case you’ve skipped that step, you don’t need to do this). Second, you can configure it to pass requests through to the real backend and thus implement data storage. Feel free to send us feedback in the comments and stay tuned for new tutorials.

Comments

  1. Sylvain Anselmino October 6, 2017 at 11:51 am

    I run your tuto and hade to resolve an error like “cannot read property ‘0’ of undefined”

    Indeed, the html sample in the “Creating Scheduler Component” (content of scheduler.component.html) has an extra “” on the first line. I resolve my problem by removing it. The content of the scheduler.component.html should be :

     
     

    • Aras Kairys (DHTMLX team) October 20, 2017 at 1:12 pm

      Hi Sylvain,
      Thank you for the comment, we’ll correct the tutorial asap.

    • Aras Kairys (DHTMLX team) October 20, 2017 at 1:34 pm

      Hi Sylvain,
      The tutorial is updated.

  2. Sylvain Anselmino October 6, 2017 at 11:54 am

    Hi again,
    Sorry for double post, html tag have been deleted in my previous post. The extra element is the closing div tag at the first line.
    Regards

  3. Jack Brv October 10, 2017 at 12:12 am

    Hi,
    It is possible to use Scheduler Extensions?

    • Aras Kairys (DHTMLX team) October 20, 2017 at 1:32 pm

      Hi Jack,
      To use extensions you should add them with dhtmlxScheduler.
      You should get something like this:
      import “dhtmlx-scheduler”
      and import “dhtmlx-scheduler/codebase/ext/[extension]”.
      E.g. import “dhtmlx-scheduler/codebase/ext/dhtmlxscheduler_timeline” for timeline extension.

      • Jack Brv October 26, 2017 at 12:33 am

        Many thanks for your help!

      • Laura November 22, 2017 at 11:21 pm

        I have downloaded the scheduler by npm but it doesn’t have the timeline extension. Where I can find it?

        • Aras Kairys (DHTMLX team) November 23, 2017 at 3:44 pm

          Hi Laura,
          Timeline view is a PRO feature of dhtmlxScheduler.
          Unfortunately, the installation of Scheduler PRO edition via NPM is not available at the moment.
          But if you are a licensed user, please create a ticket in our support system and we’ll provide you a personal NPM link.

  4. Kristian October 19, 2017 at 6:40 pm

    Good afternoon,

    I’m currently evaluating a trial version of this product for use in a project and wondered how best to implement the Timeline View within the Angular framework.

    Many thanks for any assistance you can provide!

  5. lne October 25, 2017 at 11:33 am

    Great post!
    But the version used is the “Standard Edition”
    How to use the “Professional Edition” version?
    I already bought a few months ago the “Professional Edition” version.

    • Aras Kairys (DHTMLX team) October 25, 2017 at 3:34 pm

      Hi lne,
      Unfortunately, the installation of dhtmlxScheduler PRO edition via NPM is not available at the moment.
      But as you are the licensed user, please create a ticket in our support system and we’ll provide you a personal NPM link.

      • lne November 8, 2017 at 10:58 am

        Hello,
        I have a problem with the extension “minical”
        I use dhtmlxScheduler 4.4.9 professional received from your support (by NPM)

        I imported the extension:
        import “@dhx/scheduler/ext/dhtmlxscheduler_minical”;

        When I click on the icon of the minical, I receive this error (in the Chrome console):

        dhtmlxscheduler_minical.js:10 Uncaught ReferenceError: getOffset is not defined
        at Object.scheduler.renderCalendar (dhtmlxscheduler_minical.js:10)
        at show_minical (:6:19)
        at HTMLDivElement.onclick (VM5956 scheduler:27)

        I transmitted this problem on the forum and on your support (while I pay) and I received no response!

        • Jack Brv November 8, 2017 at 12:37 pm

          I have the same problem. For the moment in my test environment i put on top of dhtmlxscheduler_minical.js these function:

          function getAbsoluteLeft(e) {
          return getOffset(e).left
          }
          function getAbsoluteTop(e) {
          return getOffset(e).top
          }
          function getOffsetSum(e) {
          for (var t = 0, i = 0; e; )
          t += parseInt(e.offsetTop),
          i += parseInt(e.offsetLeft),
          e = e.offsetParent;
          return {
          top: t,
          left: i
          }
          }
          function getOffsetRect(e) {
          var t = e.getBoundingClientRect()
          , i = 0
          , r = 0;
          if (/Mobi/.test(navigator.userAgent)) {
          var s = document.createElement(“div”);
          s.style.position = “absolute”,
          s.style.left = “0px”,
          s.style.top = “0px”,
          s.style.width = “1px”,
          s.style.height = “1px”,
          document.body.appendChild(s);
          var a = s.getBoundingClientRect();
          i = t.top – a.top,
          r = t.left – a.left,
          s.parentNode.removeChild(s)
          } else {
          var n = document.body
          , d = document.documentElement
          , o = window.pageYOffset || d.scrollTop || n.scrollTop
          , l = window.pageXOffset || d.scrollLeft || n.scrollLeft
          , h = d.clientTop || n.clientTop || 0
          , _ = d.clientLeft || n.clientLeft || 0;
          i = t.top + o – h,
          r = t.left + l – _
          }
          return {
          top: Math.round(i),
          left: Math.round(r)
          }
          }
          function getOffset(e) {
          return e.getBoundingClientRect ? getOffsetRect(e) : getOffsetSum(e)
          }

          These function are in dhtmlxscheduler.js, the problem is that the extension fails to call them

        • Stanislav November 8, 2017 at 1:53 pm

          Extensions of dhtmlxScheduler must be included only after the core file dhtmlxsheduler.js
          When you are working with script includes it means that you are adding a script tag for dhtmlxscheduler.js first, and script tags for extensions after that.

          In case of import, the situation is a more complicated, as import doesn’t guarantee that imported sources will be processed in the defined orders. If you are using webpack – try to use script-loader for scheduler’s file, which will preserve an order of includes and will fix the issue.

Leave a Reply