Features

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

Scheduler UI Builder

This project was generated using Scheduler UI Builder. You can use this visual tool to configure the Scheduler appearance and properties and generate a downloadable project

Running the Project

First, run the PHP backend project (angular-scheduler-dynamic-php-backend) using the PHP built-in web server:

Linux

php -S 127.0.0.1:8090 -t /home/daypilot/tutorials/angular-scheduler-dynamic-php-backend

Windows

C:\php\php.exe -S 127.0.0.1:8090 -t C:\Users\DayPilot\tutorials\angular-scheduler-dynamic-php-backend

Note: The PHP backend project uses a local sqlite database. It's necessary enable php_pdo_sqlite extension in php.ini file.

Now we can run the Angular 5 project (angular-scheduler-dynamic):

npm run start

Scheduler Performance Tuning

We will discuss three methods of loading Scheduler events are resources:

  1. Angular Attributes
  2. Direct Scheduler API
  3. Dynamic Event Loading

#1 Angular Attributes

This is the easiest method - the Scheduler watches the specified objects and updates itself automatically whenever a change is detected.

Events are loaded using the object specified using [events] attribute of <daypilot-scheduler> tag:

<daypilot-scheduler [events]="events"></daypilot-scheduler>

The "events" array is defined as a property of SchedulerComponent class:

export class SchedulerComponent implements AfterViewInit {

  // ...

  events: any[] = [];

}

The loadEvents() method simply assigns the event data to this.events:

  loadEvents(): void {
    let dp = this.scheduler.control;
    let start = dp.visibleStart();
    let end = dp.visibleEnd();
    this.ds.getEvents(start, end).subscribe(result => {
      this.events = result;
    });
  }

Resources are loaded using "resources" property of the config object which is mapped using [config] attribute of <daypilot-scheduler> tag:

<daypilot-scheduler [config]="config"></daypilot-scheduler>

The "resources" array is a property of "config" object:

export class SchedulerComponent implements AfterViewInit {

  // ...

  config: any = {
    resources: [],
  };

}

The loadResources() method updates the config.resources value:

  loadResources(): void {
    this.ds.getResources().subscribe(result => {
      this.config.resources = result;
    });
  }

Full source code:

import {Component, ViewChild, AfterViewInit} from "@angular/core";
import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";{}

@Component({
  selector: 'scheduler-component',
  template: `
    <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>
  `,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  config: any = {
    eventHeight: 30,
    cellWidthSpec: "Auto",
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    treeEnabled: true,
    resources: []
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {
    this.loadResources();
    this.loadEvents();
  }

  loadResources(): void {
    this.ds.getResources().subscribe(result => {
      this.config.resources = result;
    });
  }
  
  loadEvents(): void {
    let dp = this.scheduler.control;
    let start = dp.visibleStart();
    let end = dp.visibleEnd();
    this.ds.getEvents(start, end).subscribe(result => {
      this.events = result;
    });
  }

}

Advantages:

  • Easy to understand
  • Uses standard Angular way two-way data binding
  • Any data change (of the whole array or of any item) is automatically displayed
  • No explicit update() calls required

Disadvantages:

  • Performance suffers when there are many resources and/or events
  • All events need to be loaded at once

Internally, both objects are compared with the previous state during every change detection cycle. That happens very often (on every mouse move) and it can become a significant burden and slow down the whole page if the objects are large.

#2 Direct Scheduler API

This method loads the event data using the standard DayPilot.Scheduler API.

Events are loaded using direct access to DayPilot.Scheduler.events.list array. Note that the [events] attribute of <daypilot-scheduler> is not specified:

<daypilot-scheduler [config]="config" #scheduler></daypilot-scheduler>

The loadEvents() method updates "dp.events.list" directly and calls update() to update the Scheduler:

  loadEvents(): void {
    let dp = this.scheduler.control;
    let start = dp.visibleStart();
    let end = dp.visibleEnd();
    this.ds.getEvents(start, end).subscribe(result => {
      dp.events.list = result;
      dp.update();
    });
  }

Resources are loaded using direct access to DayPilot.Scheduler.resources array.

Note that the [config] attribute is required:

<daypilot-scheduler [config]="config" #scheduler></daypilot-scheduler>

But the "config" object doesn't specify "resources" property:

  config: any = {
    eventHeight: 30,
    cellWidthSpec: "Auto",
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    treeEnabled: true,
  };

The loadResources() method assigns the data to "dp.resources" and calls update():

  loadResources(): void {
    this.ds.getResources().subscribe(result => {
      let dp = this.scheduler.control;
      dp.resources = result;
      dp.update();
    });
  }

Full source code:

import {Component, ViewChild, AfterViewInit} from "@angular/core";
import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";{}

@Component({
  selector: 'scheduler-component',
  template: `
    <daypilot-scheduler [config]="config" #scheduler></daypilot-scheduler>
  `,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler: DayPilotSchedulerComponent;

  config: any = {
    eventHeight: 30,
    cellWidthSpec: "Auto",
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    treeEnabled: true
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {
    this.loadResources();
    this.loadEvents();
  }

  loadResources(): void {
    this.ds.getResources().subscribe(result => {
      let dp = this.scheduler.control;
      dp.resources = result;
      dp.update();
    });
  }
  
  loadEvents(): void {
    let dp = this.scheduler.control;
    let start = dp.visibleStart();
    let end = dp.visibleEnd();
    this.ds.getEvents(start, end).subscribe(result => {
      dp.events.list = result;
      dp.update();
    });
  }

}

Advantages:

  • No performance overhead

Disadvantages

  • Any change requires an explicit update() call to be displayed
  • All events need to be loaded at once

#3 Dynamic Event Loading

The previous methods will work well with small (100 events) and medium (1,000 events) data sets. The Scheduler implements a number of optimizations that improve the rendering speed - by default only the visible viewport (plus a certain buffer) is rendered, no matter how many events are loaded.

However, for large data sets (10,000 events) it won't be enough. Although the events are not rendered they still need to be loaded and processed. If that starts to hurt the Angular application performance we need to switch to dynamic loading - only a limited set of events will be loaded and displayed. 

With dynamic loading enabled, the events are loaded during scrolling. That introduces a small lag before the events are available - but it will scale almost linearly: the scrolling will be smooth and the lag will be constant.

We need to enable dynamic loading first:

  config: any = {
    dynamicLoading: true,
    // ...
  };

This enables onScroll event which will be fired whenever the Scheduler viewport changes (scrolling, window resizing). We will handle this event and request the data for the current viewport:

  config: any = {
    dynamicLoading: true,
    onScroll: args => {
      console.log("onScroll called");
      let dp = this.scheduler.control;
      let viewport = dp.getViewPort();
      let from = viewport.start;
      let to = viewport.end;
      let rstart = viewport.resources[0];
      let rend = viewport.resources[viewport.resources.length - 1];

      args.async = true;
      this.ds.getEventsForViewport(from, to, rstart, rend).subscribe(result => {
        args.events = result;
        args.loaded();
      });
    },
    // ...
  };

Full source code:

import {Component, ViewChild, AfterViewInit} from "@angular/core";
import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";{}

@Component({
  selector: 'scheduler-component',
  template: `
    <daypilot-scheduler [config]="config" #scheduler></daypilot-scheduler>
  `,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler: DayPilotSchedulerComponent;

  config: any = {
    dynamicLoading: true,
    onScroll: args => {
      console.log("onScroll called");
      let dp = this.scheduler.control;
      let viewport = dp.getViewPort();
      let from = viewport.start;
      let to = viewport.end;
      let rstart = viewport.resources[0];
      let rend = viewport.resources[viewport.resources.length - 1];

      args.async = true;
      this.ds.getEventsForViewport(from, to, rstart, rend).subscribe(result => {
        args.events = result;
        args.loaded();
      });
    },
    // ...
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {
    this.loadResources();
  }

  loadResources(msg?: string) {
    this.ds.getResources().subscribe(result => {
      let dp = this.scheduler.control;
      dp.resources = result;
      dp.update();
      if (msg) {
        dp.message(msg);
      }
    });
  }

  loadEvents() {
    let dp = this.scheduler.control;
    let start = dp.visibleStart();
    let end = dp.visibleEnd();
    this.ds.getEvents(start, end).subscribe(result => {
      dp.events.list = result;
      dp.update();
    });
  }

}