Features

  • How to add a dynamically-created component to Angular Scheduler events.
  • Create an HTML element from a dynamic component
  • Inserting the component using onDomAddEvent event of the Scheduler
  • Component cleanup in onDomRemoveEvent event of the Scheduler
  • 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. Buy a license.

Angular Scheduler Event Customization

angular-scheduler-event-content-customization.png

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 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.png

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;
  public data: any;

  click(e) {
    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, ApplicationRef, Component, ComponentFactoryResolver, ComponentRef, Injector, ViewChild} 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 resolver: ComponentFactoryResolver,
              private injector: Injector,
              private app: ApplicationRef) {
  }

  createLinkComponent(text: string, data?: any): ComponentRef<LinkComponent> {
    const factory = this.resolver.resolveComponentFactory(LinkComponent);
    const component: ComponentRef<LinkComponent> = factory.create(this.injector);

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

    this.app.attachView(component.hostView);

    return component;
  }
  
  // ...

}

The createLinkComponent() method creates a new instance of the component, sets its properties (text, data) and adds the component to the Angular component tree (ApplicationRef.attachView() method).

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 onDomAddEvent event handler:

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

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

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

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

The onDomAddEvent and onDomRemoveEvent event handlers are supported in DayPilot Pro for JavaScript since build 2018.2.3304.

Prevent Event Bubbling

angular-scheduler-dynamic-event-component-click.png

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) {
    e.stopPropagation();
    // ...
  }
}

Performance

Please note that onDomAddEvent 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)

scheduler.component.ts

import {
  AfterViewInit,
  ApplicationRef,
  Component,
  ComponentFactoryResolver,
  ComponentRef,
  Injector,
  ViewChild
} 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: any[] = [];

  config: any = {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    eventHeight: 30,
    treeEnabled: true,
    eventDeleteHandling: "Update",
    onDomAddEvent: args => {
      console.log("Creating LinkComponent for " + args.e.text());
      const component = this.createLinkComponent('details', args.e.data);
      args.element = component.location.nativeElement;
      args.component = component;
    },
    onDomRemoveEvent: args => {
      console.log("Destroying LinkComponent for " + args.e.text());
      this.app.detachView(args.component.hostView);
      args.component.destroy();
    },
    onEventClick: args => {
      console.log("default event click action");
    },
    onTimeRangeSelected: args => {
      var dp = this.scheduler.control;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    }
  };

  constructor(private ds: DataService,
              private resolver: ComponentFactoryResolver,
              private injector: Injector,
              private app: ApplicationRef) {
  }

  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 factory = this.resolver.resolveComponentFactory(LinkComponent);
    const component: ComponentRef<LinkComponent> = factory.create(this.injector);

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

    this.app.attachView(component.hostView);

    return component;
  }


}

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;
  public data: any;

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

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 { }