Features

  • This project uses Angular Scheduler component that displays a timeline for multiple resources.

  • If none of the predefined Scheduler timeline scale options meet your needs you can generate your own timeline by defining the individual cells.

  • You can use custom cell duration (anything starting from 1 second).

  • The Angular Scheduler supports a non-continuous timeline (certain time ranges are hidden).

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 with 30-Second Cells

This example uses the timeline property of the Scheduler configuration to specify a custom timeline. This property is used if the Scheduler is switched to manual scale mode (scale: "Manual").

config = signal<DayPilot.SchedulerConfig>({
  // ...
  scale: "Manual",
  timeline: this.getTimeline(),
  // ...
});

The timeline is generated using the getTimeline() method. It returns an array of timeline cells. The array items specify start and end of each timeline cell. 

The following method generates a timeline for the specified date (today is used if the target date is not set). Each cell has a duration of 30 seconds (that makes 2880 cells per day).

getTimeline(date: DayPilot.Date | string = DayPilot.Date.today()): DayPilot.TimelineData[] {
  const day = new DayPilot.Date(date).getDatePart();
  const timeline = [];

  for (let i = 0; i < 2880; i++) {
    const start = day.addSeconds(i * 30);
    timeline.push({
      start: start,
      end: start.addSeconds(30)
    });
  }

  return timeline;
};

The time headers group the cells by day, hour, and minute. Each minute spans two 30-second cells. To display gaps in a manual timeline, omit the cells for the time ranges you want to hide. The example below keeps the full day visible.

Select a date using the Navigator to generate a new timeline. The configuration is stored in an Angular signal; config.update() replaces the timeline and notifies the Scheduler. The Navigator handler also calls loadEvents() with the selected day’s start and end. The sample service keeps one demonstration event on today’s date and filters it by that range.

Source Code

Here is the full source code for our Angular Scheduler component, which defines a custom timeline using 30-second time slots.

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

@Component({
  selector: 'scheduler-component',
  standalone: true,
  imports: [DayPilotModule],
  providers: [DataService],
  template: `
    <div style="display: flex">
      <div style="margin-right: 10px;">
        <daypilot-navigator [config]="navConfig"></daypilot-navigator>
      </div>
      <div style="flex: 1; min-width: 0;">
        <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>
      </div>
    </div>
  `,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  events = signal<DayPilot.EventData[]>([]);

  config = signal<DayPilot.SchedulerConfig>({
    timeHeaders: [
      {groupBy: "Day", format: "d MMMM, yyyy"},
      {groupBy: "Hour"},
      {groupBy: "Minute", format: "mm:00"}
    ],
    scale: "Manual",
    timeline: this.getTimeline(),
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: async (args) => {
      const scheduler = args.control;
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      scheduler.clearSelection();
      if (modal.canceled) { return; }
      scheduler.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    },
    onBeforeEventRender: args => {
      args.data.backColor = args.data.tags?.color ?? "#e69138";
      args.data.borderColor = "darker";
      args.data.fontColor = "#fff";
    },
    treeEnabled: true,
    durationBarVisible: false,
  });

  navConfig: DayPilot.NavigatorConfig = {
    showMonths: 3,
    onTimeRangeSelected: args => {
      this.config.update(c => ({ ...c, timeline: this.getTimeline(args.day) }));
      this.loadEvents(args.day, args.day.addDays(1));
    }
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {
    this.ds.getResources().subscribe(result => this.config.update(c => ({ ...c, resources: result })));

    const from = this.scheduler.control.visibleStart();
    const to = this.scheduler.control.visibleEnd();
    this.loadEvents(from, to);
  }

  loadEvents(from: DayPilot.Date, to: DayPilot.Date) {
    this.ds.getEvents(from, to).subscribe(result => {
      this.events.set(result);
    });
  }

  getTimeline(date: DayPilot.Date | string = DayPilot.Date.today()): DayPilot.TimelineData[] {
    const day = new DayPilot.Date(date).getDatePart();
    const timeline = [];

    for (let i = 0; i < 2880; i++) {
      const start = day.addSeconds(i * 30);
      timeline.push({
        start: start,
        end: start.addSeconds(30)
      });
    }

    return timeline;
  };

}