Features
Angular Scheduler component from DayPilot Pro for JavaScript
Configured to load events dynamically during scrolling
REST/JSON backend written in PHP
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.
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:
Angular Attributes
Direct Scheduler API
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});
});
}
}