Overview

  • Configure the Angular Scheduler component to support infinite scrolling and load events on demand

  • Pre-load events for the areas next to the viewport

  • Includes a trial version of DayPilot Pro for JavaScript (see 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.

How To Enable Infinite Scrolling

The Scheduler infinite scrolling feature can be enabled using infiniteScrollingEnabled property.

In most cases, it's also necessary to adjust the default values of infiniteScrollingMargin and infiniteScrollingStepDays.

  • The default value of infiniteScrollingMargin is 50 (pixels). That means the additional date range will be loaded when the viewport reaches the 50-pixel margin at the start/end of the current range. We will increase the value to 200 (pixels).

  • The default value of infiniteScrollingStepDays is 30. Usually, it's a good choice to use the number of days that is displayed in the viewport. We will use 70 (days).

  • Make sure that days is at least three times the viewport.

In our case (cellDuration: 40, scale: "Day"), the viewport displays about 60 days on a 2560px screen.

config: DayPilot.SchedulerConfig = {
  infiniteScrollingEnabled: true,
  infiniteScrollingMargin: 200,
  infiniteScrollingStepDays: 70,
  days: 300,
  // ...
};

How to Load Events During Scrolling

When infinite scrolling is enabled, it's necessary to load the events dynamically during scrolling.

Dynamic event loading can be enabled using dynamicLoading property. When the viewport content changes, the Scheduler will fire the onScroll event and request new data.

The key elements of the onScroll event handler:

  • The args.async = true option postpones the args.events processing until args.loaded() is called. This is useful when you need to asynchronously load the data from the server (which is the typical scenario). The async mode is enabled by default.

  • The args.clearEvents = true option deletes all events that were previously loaded. This makes the loading faster because it doesn't have compare the old and new data. This mode is enabled by default.

  • The viewport boundaries and content can be read from args.viewport. In addition to start and end (which we use in this examples) it also provides an array of resources IDs in resources.

Do not use the [events] attribute of the <daypilot-scheduler> tag to load the initial data - the onScroll event will be fired right after the initialization.

config: DayPilot.SchedulerConfig = {
  dynamicLoading: true,
  dynamicEventRendering: false,
  onScroll: (args) => {
    // these values are default since 2020.1.4276
    args.async = true;
    args.clearEvents = true;

    // load events in advance, +/- one month
    const from = args.viewport.start;
    const to = args.viewport.end;

    this.ds.getEvents(from, to).subscribe(result => {
      args.events = result;
      args.loaded();
    });
  },
  
  // ...

};

How to Pre-Load Events for Adjacent Areas

The Scheduler uses progressive event rendering by default - events are only added to the DOM when they are moved to the viewport. This improves performance of the typical scenario where lots of events are loaded at once. However, it introduces a slight delay before the events are rendered and in combination with dynamic loading it causes double event rendering after scrolling (first when they move into the viewport and second when they are reloaded from the server).

In our project, we will turn off the progressive event rendering using dynamicEventRendering. In order to provide better user experience during scrolling, we will also load events for the areas close to the current viewport (minus one month and plus one month). These events will be rendered immediately and will be visible during scrolling.

config: DayPilot.SchedulerConfig = {
  dynamicEventRendering: false,
  onScroll: (args) => {

    // load events in advance, +/- one month
    const from = args.viewport.start.addMonths(-1);
    const to = args.viewport.end.addMonths(1);

    this.ds.getEvents(from, to).subscribe(result => {
      args.events = result;
      args.loaded();
    });
  },
  // ...
};

How to Set the Initial Scrollbar Position

By default the viewport will display the current date. If you want to set a different starting scrollbar position, you can set it using scrollTo() method in ngAfterViewInit().

Note that we didn't specify startDate property in the config. When infinite scrolling is enabled, the internal startDate value is updated dynamically. If there is a fixed startDate value in the config, the Scheduler resets the current position to that date after every config change.

ngAfterViewInit(): void {

  // use scrollTo method instead of config.startDate
  // if not called, it will display the current date
  this.scheduler.control.scrollTo("2020-06-12");

}

Things to Avoid

  • Do not set the [events] attribute of the <daypilot-scheduler> tag.

  • Do not add startDate property to the config object. That would reset the scrollbar position to the specified date on every config change.

Full Source Code

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

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

  @ViewChild('scheduler', {static: false})
  scheduler: DayPilotSchedulerComponent;

  lastStart: DayPilot.Date = null;

  config: DayPilot.SchedulerConfig = {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: 300,
    onTimeRangeSelected: function (args) {
      var dp = this;
      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
        }));
      });
    },
    dynamicLoading: true,
    infiniteScrollingEnabled: true,
    infiniteScrollingMargin: 200,
    infiniteScrollingStepDays: 70,
    dynamicEventRendering: false,
    onScroll: (args) => {
      // don't activate for vertical-only scrolling
      if (this.lastStart === args.viewport.start) {
        return;
      }
      else {
        this.lastStart = args.viewport.start;
      }

      // these values are default since 2020.1.4276
      args.async = true;
      args.clearEvents = true;

      // load events in advance, +/- one month
      const from = args.viewport.start.addMonths(-1);
      const to = args.viewport.end.addMonths(1);

      this.ds.getEvents(from, to).subscribe(result => {
        args.events = result;
        args.loaded();
      });
    },
    treeEnabled: true,
  };

  constructor(private ds: DataService) {
  }

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

    // use scrollTo method instead of config.startDate
    // if not called, it will display the current date
    this.scheduler.control.scrollTo("2020-06-12");

  }

}