
| Demo | |
|---|---|
| Downloads | |
| Languages | TypeScript |
| Technologies | angular |
Overview
-
How to add next/previous buttons that will change the current view of the Angular Scheduler component
-
Event loading based on view change detection event that is fired after every Scheduler update
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.
How to Configure the Scheduler to Display the Current Month
The visible date range of the Angular Scheduler component can be set using startDate and days properties of the config signal. The following configuration displays the current month. firstDayOfMonth() sets the start to day 1, and daysInMonth() supplies the correct number of days:
import {Component, ViewChild, AfterViewInit, signal, OnDestroy} from '@angular/core';
// ...
export class SchedulerComponent implements AfterViewInit, OnDestroy {
// ...
config = signal<DayPilot.SchedulerConfig>({
timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
scale: "Day",
days: DayPilot.Date.today().daysInMonth(),
startDate: DayPilot.Date.today().firstDayOfMonth(),
// ...
});
// ...
}
How to Change the Month using Next, Previous and Today Buttons
If you want to change the current range using Next/Today/Previous buttons you need to add a (click) handler that changes the startDate value. In case of a month view, it is also necessary to update the days value because the number of days depends on the selected month:
HTML template:
<button (click)="previous()">Previous</button>
<button (click)="today()">Today</button>
<button (click)="next()">Next</button>
TypeScript:
previous(): void {
const startDate = new DayPilot.Date(this.config().startDate!).addMonths(-1);
this.config.update(c => ({ ...c, startDate, days: startDate.daysInMonth() }));
}
today(): void {
const startDate = DayPilot.Date.today().firstDayOfMonth();
this.config.update(c => ({ ...c, startDate, days: startDate.daysInMonth() }));
}
next(): void {
const startDate = new DayPilot.Date(this.config().startDate!).addMonths(1);
this.config.update(c => ({ ...c, startDate, days: startDate.daysInMonth() }));
}
Each handler replaces the configuration value using config.update(). Updating both properties together keeps the month boundary and length in sync, including February and year changes. The Today button returns to the current month rather than scrolling to a single day.
Loading Scheduler Events for the New Month
The template reads the signals using [config]="config()" and [events]="events()". Angular passes their current values to the Scheduler wrapper, which detects configuration changes and updates the view. This value binding preserves the wrapper’s viewChange notification path. When changing the visible month, it's necessary to load the events for the new date range.
It's possible to add the event-loading code to the next(), previous() and today() methods but we will use (viewChange) event that is fired whenever the Scheduler component detects a config change and updates itself. This keeps event loading in one place for the initial view and subsequent month changes.
HTML template:
<daypilot-scheduler [config]="config()" [events]="events()" (viewChange)="schedulerViewChanged($event)" #scheduler></daypilot-scheduler>
TypeScript
export class SchedulerComponent implements AfterViewInit, OnDestroy {
// ...
events = signal<DayPilot.EventData[]>([]);
private eventRequest?: Subscription;
// ...
schedulerViewChanged(args: {visibleRangeChanged: boolean}): void {
if (args.visibleRangeChanged) {
const from = this.scheduler.control.visibleStart();
const to = this.scheduler.control.visibleEnd();
this.eventRequest?.unsubscribe();
this.events.set([]);
this.eventRequest = this.ds.getEvents(from, to).subscribe(result => {
this.events.set(result);
});
}
}
ngOnDestroy(): void {
this.eventRequest?.unsubscribe();
}
}
We detect whether the view change included a date range change by checking the args.visibleRangeChanged property. Resource and event updates can also trigger view notifications; this guard prevents those updates from starting another event request. visibleStart() and visibleEnd() provide the range to load, with an exclusive end.
Before loading a new range, we cancel the previous subscription and clear the old events. This prevents a slower response for a previous month from replacing the current results. The subscription is also canceled when the component is destroyed.
The included data service simulates a request with a short delay and returns events that overlap the requested range. Its three sample events belong to the month in which the app starts, so adjacent months are empty; click Today to return to them. In an application with a backend, send from and to to the server and apply the same overlap filter. The demo uses in-memory data; interactive event edits are not persisted across reloads or month navigation.
Full Source Code
import {Component, ViewChild, AfterViewInit, signal, OnDestroy} from '@angular/core';
import {DayPilot, DayPilotModule, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from './data.service';
import {Subscription} from 'rxjs';
@Component({
selector: 'scheduler-component',
standalone: true,
imports: [DayPilotModule],
providers: [DataService],
template: `
<div class="toolbar">
<button (click)="previous()">Previous</button>
<button (click)="today()">Today</button>
<button (click)="next()">Next</button>
</div>
<daypilot-scheduler [config]="config()" [events]="events()" (viewChange)="schedulerViewChanged($event)" #scheduler></daypilot-scheduler>
`,
styles: [`
.toolbar {
margin: 10px 0px;
}
.toolbar button {
background-color: #f3f3f3;
border: 1px solid #c0c0c0;
color: #333;
padding: 8px 0px;
width: 100px;
border-radius: 2px;
cursor: pointer;
margin-right: 5px;
}
`]
})
export class SchedulerComponent implements AfterViewInit, OnDestroy {
@ViewChild('scheduler', {static: true})
scheduler!: DayPilotSchedulerComponent;
events = signal<DayPilot.EventData[]>([]);
private eventRequest?: Subscription;
config = signal<DayPilot.SchedulerConfig>({
timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
scale: "Day",
days: DayPilot.Date.today().daysInMonth(),
startDate: DayPilot.Date.today().firstDayOfMonth(),
timeRangeSelectedHandling: "Enabled",
onTimeRangeSelected: async (args) => {
const dp = args.control;
const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
dp.clearSelection();
if (modal.canceled) { return; }
dp.events.add({
start: args.start,
end: args.end,
id: DayPilot.guid(),
resource: args.resource,
text: modal.result
});
},
treeEnabled: true,
});
constructor(private ds: DataService) {
}
ngAfterViewInit(): void {
this.ds.getResources().subscribe(result => {
this.config.update(c => ({ ...c, resources: result }));
});
}
previous(): void {
const startDate = new DayPilot.Date(this.config().startDate!).addMonths(-1);
this.config.update(c => ({ ...c, startDate, days: startDate.daysInMonth() }));
}
today(): void {
const startDate = DayPilot.Date.today().firstDayOfMonth();
this.config.update(c => ({ ...c, startDate, days: startDate.daysInMonth() }));
}
next(): void {
const startDate = new DayPilot.Date(this.config().startDate!).addMonths(1);
this.config.update(c => ({ ...c, startDate, days: startDate.daysInMonth() }));
}
schedulerViewChanged(args: {visibleRangeChanged: boolean}): void {
if (args.visibleRangeChanged) {
const from = this.scheduler.control.visibleStart();
const to = this.scheduler.control.visibleEnd();
this.eventRequest?.unsubscribe();
this.events.set([]);
this.eventRequest = this.ds.getEvents(from, to).subscribe(result => {
this.events.set(result);
});
}
}
ngOnDestroy(): void {
this.eventRequest?.unsubscribe();
}
}
DayPilot




