Overview

  • How to customize the Angular Scheduler row headers using Angular components.

  • Use daypilotTemplate="rowHeader" to display a custom component in each row header. Angular bindings supply the resource name and connect the button event handlers.

  • Includes DayPilot Lite, the open-source edition of DayPilot for JavaScript (see License below).

License

The tutorial source code and DayPilot Lite are licensed under the Apache License 2.0. Third-party libraries use their respective licenses; see the licensing information included in the sample. For additional commercial features, DayPilot Pro is available: Buy a license.

Angular Scheduler Configuration

Let’s start with an Angular project generated using the online UI Builder tool. This example uses standalone components and @daypilot/daypilot-lite-angular 6.2.0 or later. Version 6.2.0 introduced ng-template support in DayPilot Lite for Angular, including custom row-header content.

The config signal initializes the Scheduler with a daily timeline for the current month. The rowHeaderWidth setting leaves room for the resource buttons. Selecting a time range opens a dialog and adds an event to the selected resource:

config = signal<DayPilot.SchedulerConfig>({
  timeHeaders: [{groupBy: 'Month'}, {groupBy: 'Day', format: 'd'}],
  scale: 'Day',
  rowHeaderWidth: 110,
  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
    });
  }
});

We load the resource data in ngAfterViewInit(). The resources will be displayed as rows. Updating the signal with a new configuration object lets the Angular wrapper apply the resource list when the simulated request completes:

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

The DataService class generates a sample set of seven resources. Each resource has an ID used by events and a name displayed in the row header:

import {Injectable} from '@angular/core';
import {DayPilot} from '@daypilot/daypilot-lite-angular';
import {Observable, of, delay} from 'rxjs';

@Injectable()
export class DataService {
  resources: DayPilot.ResourceData[] = [
    {name: 'Resource 1', id: 'R1'},
    {name: 'Resource 2', id: 'R2'},
    {name: 'Resource 3', id: 'R3'},
    {name: 'Resource 4', id: 'R4'},
    {name: 'Resource 5', id: 'R5'},
    {name: 'Resource 6', id: 'R6'},
    {name: 'Resource 7', id: 'R7'}
  ];

  getResources(): Observable<DayPilot.ResourceData[]> {
    // Simulate an HTTP request.
    return of(this.resources).pipe(delay(200));
  }
}

Custom Component: ResourceComponent

Now we can create a new Angular component that displays a button with the specified name.

It also handles two events (click and dblclick). A click writes a message to the browser console; a double-click also opens a DayPilot alert dialog. The browser fires click events during a double-click, so both handlers can run for that gesture.

The text input specifies the button text and accessible label. The component keeps the blue button styling inside its own Angular styles:

import {Component, Input} from '@angular/core';
import {DayPilot} from '@daypilot/daypilot-lite-angular';

@Component({
  selector: 'app-resource',
  standalone: true,
  template: `<button [attr.aria-label]="text" (click)="click()" (dblclick)="doubleClick()">{{text}}</button>`,
  styles: [`
    button {
      background-color: #3c78d8;
      border: 1px solid #1155cc;
      color: #fff;
    }
  `]
})
export class ResourceComponent {
  @Input() text = '';

  click() {
    console.log('button clicked');
  }

  doubleClick() {
    console.log('button double-clicked');
    DayPilot.Modal.alert('Button double clicked');
  }
}

Add ResourceComponent to the Scheduler Row Header

Place an ng-template with daypilotTemplate="rowHeader" directly inside the Scheduler element. The template supplies the content of each row header:

<daypilot-scheduler [config]="config">
  <ng-template daypilotTemplate="rowHeader"
               daypilotTemplateFor="scheduler"
               let-row="row">
    <app-resource [text]="row.name"></app-resource>
  </ng-template>
</daypilot-scheduler>

The named binding let-row="row" exposes the current DayPilot.Row. Binding row.name to the child component’s text input gives every button its resource name. The daypilotTemplateFor="scheduler" qualifier identifies the Scheduler context for Angular template type checking.

Import both DayPilotModule and ResourceComponent in the containing standalone component. The module supplies the Scheduler and template directive; the child import makes app-resource available in the template:

import {ResourceComponent} from './resource.component';

@Component({
  selector: 'scheduler-component',
  standalone: true,
  imports: [DayPilotModule, ResourceComponent],
  providers: [DataService],
  // ...
})
export class SchedulerComponent implements AfterViewInit {
  // ...
}

Component Lifecycle

The Angular wrapper creates and disposes the template views as row headers are rendered and removed. This replaces the manual ViewContainerRef.createComponent() and ComponentRef.destroy() handlers; the application does not need to store component references or call change detection after assigning the button text.

With progressive row rendering, headers can be removed and rendered again during vertical scrolling. Store values that must survive a redraw in application state, keyed by resource ID, rather than only in a child component instance. Here, each button simply reads the resource name and has no persistent local state.

History

October 22, 2026: Switched to DayPilot Lite for Angular 6.2.0 to demonstrate its new ng-template support.