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.

For an introduction on using the Angular Scheduler component with a PHP backend please see Angular Scheduler Tutorial (TypeScript + PHP/MySQL).

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 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

How to Load Scheduler Events using 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: DayPilot.EventData[] = [];

}

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

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

Resources are loaded using the 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: DayPilot.SchedulerConfig = {
    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: DayPilot.EventData[] = [];

  config: DayPilot.SchedulerConfig = {
    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() {
    const dp = this.scheduler.control;
    const start = dp.visibleStart();
    const 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.

How to Load Scheduler Events using the Direct 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 the Angular Scheduler events using update() call:

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

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: DayPilot.SchedulerConfig = {
  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 calls the update() method and request an update of the resources:

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

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: DayPilot.SchedulerConfig = {
    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(resources => {
      const dp = this.scheduler.control;
      dp.update({resources});
    });
  }
  
  loadEvents(): void {
    const dp = this.scheduler.control;
    const start = dp.visibleStart();
    const end = dp.visibleEnd();
    this.ds.getEvents(start, end).subscribe(events => {
      dp.update({events});
    });
  }

}

Advantages:

  • No performance overhead

Disadvantages

  • Any change requires an explicit update() call to be displayed

  • All events need to be loaded at once

How to Load Scheduler Events Dynamically during Scrolling?

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: DayPilot.SchedulerConfig = {
  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: DayPilot.SchedulerConfig = {
  dynamicLoading: true,
  onScroll: args => {
    const dp = this.scheduler.control;
    const viewport = dp.getViewPort();
    const from = viewport.start;
    const to = viewport.end;
    const rstart = viewport.resources[0];
    const rend = viewport.resources[viewport.resources.length - 1];

    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: DayPilot.SchedulerConfig = {
    dynamicLoading: true,
    onScroll: args => {
      const dp = this.scheduler.control;
      const viewport = dp.getViewPort();
      const from = viewport.start;
      const to = viewport.end;
      const rstart = viewport.resources[0];
      const rend = viewport.resources[viewport.resources.length - 1];

      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(resources => {
      const dp = this.scheduler.control;
      dp.update({resources});
      if (msg) {
        dp.message(msg);
      }
    });
  }

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

}