Features

  • How to add a dynamically-created component to Angular Scheduler events.

  • Create an HTML element from a dynamic component

  • Inserting the component using onBeforeEventDomAdd event of the Scheduler component

  • Component cleanup in onBeforeEventDomRemove event of the Scheduler component

  • Includes a trial version of DayPilot Pro for JavaScript (see also License below)

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.

Angular Scheduler Event Customization

angular scheduler event content customization

The Angular Scheduler component offers several tools to customize the event content:

  • By default the event displays the value of the text property specified using the event data object.

  • You can override the text with custom HTML using the html property of the event data object.

  • You can customize the event HTML in onBeforeEventRender event handler. This event handler lets you create the HTML dynamically from the event properties (you don't have to create the HTML on the server and send it as part of the JSON string).

  • You can add dynamic elements called active areas at custom position inside the event element.

In addition to these options, you can also provide custom HTML element that will be used as event body. This tutorial shows how to create the element dynamically from an Angular component and insert it into the Scheduler event.

Creating an Angular Component Dynamically

angular scheduler dynamic event component

Let's create a simple LinkComponent which displays a hyperlink using <a> element:

import {Component} from '@angular/core';
import {DayPilot} from "daypilot-pro-angular";

@Component({
  selector: 'link-component',
  template: `<a href="#" (click)="click($event)">{{text}}</a>`
})
export class LinkComponent  {

  public text: string = "link";
  public data: any;

  click(e: MouseEvent) {
    e.preventDefault();
    e.stopPropagation();
    DayPilot.Modal.alert("Event id: " + this.data.id);
  }
}

We will use createLinkComponent() method to create a component instance dynamically.

import {AfterViewInit, Component, ComponentRef, ViewChild, ViewContainerRef} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from './data.service';
import {LinkComponent} from "./link.component";

@Component({
  selector: 'scheduler-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  constructor(private ds: DataService,
              private viewContainerRef: ViewContainerRef) {
  }

  createLinkComponent(text: string, data?: any): ComponentRef<LinkComponent> {
    const component: ComponentRef<LinkComponent> = this.viewContainerRef.createComponent(LinkComponent);

    component.instance.text = text;
    component.instance.data = data;
    component.changeDetectorRef.detectChanges();

    return component;
  }
  
  // ...

}

The createLinkComponent() method creates a new instance of the component and sets its properties (text, data). It is necessary to apply the changes by calling detectChanges() method of the change detector.

Don't forget to add the LinkComponent to entryComponents section of the SchedulerModule:

@NgModule({
  // ...
  entryComponents: [LinkComponent],
  // ...
})
export class SchedulerModule { }

As soon as we have the component reference we can add it to the event body using onBeforeEventDomAdd event handler:

config: DayPilot.Scheduler = {
  
  // ...
  
  onBeforeEventDomAdd: args => {
    console.log("Creating LinkComponent for " + args.e.text());
    const component = this.createLinkComponent('details', args.e.data);
    args.element = component.location.nativeElement;
    (<any>args).component = component;
  },
  
}

If you specify args.data.element in onBeforeEventDomAdd the specified HTML element will be displayed in the event body instead of the default HTML (event text or html).

There is also an onBeforeEventDomRemove event handler which is invoked when the Scheduler removes the event from the DOM. We will use it to destroy the component:

config: DayPilot.Scheduler = {
  
  // ...
  
  onBeforeEventDomRemove: args => {
    console.log("Destroying LinkComponent for " + args.e.text());
    const component = (<any>args).component;
    component.destroy();
  },
  
}

The onBeforeEventDomAdd and onBeforeEventDomRemove event handlers are supported in DayPilot Pro for JavaScript since build 2019.3.3967.

Prevent Event Bubbling

angular scheduler dynamic event component click

The Scheduler event element defines its own event handlers. If you handle the click event in the inner component (LinkComponent) it's necessary to prevent the event from bubbling to the parents using stopPropagation() call:

import {Component} from '@angular/core';

@Component({
  selector: 'simple-component',
  template: `<a href="#" (click)="click($event)">{{text}}</a>`
})
export class LinkComponent  {

  click(e: MouseEvent) {
    e.stopPropagation();
    // ...
  }
}

Performance

Please note that onBeforeEventDomAdd is called for every event and creating the component takes some time. Rendering complex component inside events may affect performance, especially when there are many events.

The Scheduler renders only the events for the current viewport (see progressive event rendering). This reduces the time needed for initial rendering but the events will be rendered later (during scrolling). Generally, you should try to keep the event structure as simple as possible.

Full Source Code (TypeScript)

This is the source code of the main component with the Angular Scheduler (scheduler.component.ts):

import {AfterViewInit, Component, ComponentRef, ViewChild, ViewContainerRef} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from './data.service';
import {LinkComponent} from "./link.component";

@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 = {
    timeHeaders: [{groupBy: "Month"},{groupBy: "Day", format:"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    treeEnabled: true,
    eventDeleteHandling: "Update",
    onBeforeEventDomAdd: args => {
      console.log("Creating LinkComponent for " + args.e.text());
      const component = this.createLinkComponent('details', args.e.data);
      args.element = component.location.nativeElement;
      (<any>args).component = component;
    },
    onBeforeEventDomRemove: args => {
      console.log("Destroying LinkComponent for " + args.e.text());
      const component = (<any>args).component;
      component.destroy();
    },
    onEventClick: args => {
      console.log("default event click action");
    },
    onTimeRangeSelected: async args => {
      const  dp = this.scheduler.control;
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      dp.clearSelection();
      if (!modal.result) { return; }
      dp.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    }
  };

  constructor(private ds: DataService,
              private viewContainerRef: ViewContainerRef) {
  }

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

    const from = this.scheduler.control.visibleStart();
    const to = this.scheduler.control.visibleEnd();
    this.ds.getEvents(from, to).subscribe(result => {
      this.events = result;
    });
  }

  createLinkComponent(text: string, data?: any): ComponentRef<LinkComponent> {
    const component: ComponentRef<LinkComponent> = this.viewContainerRef.createComponent(LinkComponent);

    component.instance.text = text;
    component.instance.data = data;
    component.changeDetectorRef.detectChanges();

    return component;
  }

}

This is the source code of the component that is displayed inside Scheduler events (link.component.ts):

import {Component} from '@angular/core';
import {DayPilot} from "daypilot-pro-angular";

@Component({
  selector: 'link-component',
  template: `<a href="#" (click)="click($event)">{{text}}</a>`
})
export class LinkComponent  {

  public text: string = "link";
  public data: any;

  click(e: MouseEvent) {
    e.preventDefault();
    e.stopPropagation();
    DayPilot.Modal.alert("Event id: " + this.data.id);
  }
}

Source code of the Angular module with the required component registration (scheduler.module.ts):

import {DataService} from './data.service';
import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {SchedulerComponent} from './scheduler.component';
import {DayPilotModule} from 'daypilot-pro-angular';
import {HttpClientModule} from '@angular/common/http';
import {LinkComponent} from "./link.component";

@NgModule({
  imports:      [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    DayPilotModule
  ],
  declarations: [
    SchedulerComponent,
    LinkComponent
  ],
  entryComponents: [LinkComponent],
  exports:      [ SchedulerComponent ],
  providers:    [ DataService ]
})
export class SchedulerModule { }