angularfullcalendarfullcalendar-5rrulefullcalendar-6

FullCalendar Angular, in eventDidMount only last setProp is working after navigation button is pressed


I am using FullCalendar in Angular with rrule pluging and repeating events. I want to change the backgroundColor, textColor etc. inside the eventDidMount callback. However, on using setting multiple props inside the eventDidMount hook, only the last one in order works after any navigation button is pressed. On using backend calls for event, it shows same behavior even before pressing the navigation button. Please provide a way to change multiple properties of the repeating event, based on it's start DateTime.

Here is a demo showcasing the problem - https://stackblitz.com/edit/stackblitz-starters-r8rgqd?file=src%2Fcalendar%2Fcalendar.component.ts

Calendar Options And Event Did Mount:

calendarOptions = {
    plugins: [timeGridPlugin, rrulePlugin],
    initialView: 'timeGridWeek',
    eventSources: [
      {// any dummy repeating data with rrule
        events: (a: any, success: any, c: any) =>
          this.initDummyEvents(a, success, c),
      },
    ],
    eventDidMount: (mountArg: EventMountArg) => this.onEventMounted(mountArg),
  };
  constructor() {}

  ngOnInit() {}

  onEventMounted(mountArg: EventMountArg) {
    if (mountArg.event.extendedProps['type'] === 'break_event') {
      // ONLY LAST (textColor) WILL WORK IF NAVIGATION BUTTON IS PRESSE
      mountArg.event.setProp('backgroundColor', '#FDDD9F');
      mountArg.event.setProp('borderColor', '#202020');
      mountArg.event.setProp('textColor', '#202020');
    }
    // I am not using the eventDataTransform callback because I need the calculated start Date of expanded event
  }

Solution

  • I found in the official fullcalendar angular documentation, it is mentioned, specifically for angular I think, that:-

    If you want to modify options that are complex objects, like headerToolbar or events, you’ll need to make a copy of the object, make your change, and then reassign it. If you DO NOT want to do this, you can have the angular connector recursively search for changes within your objects, though this comes at a slight performance cost. Set the deepChangeDetection prop to "true"

    So following the first option in the suggestion, I ended up making the following changes:

    Working example on stackblitz. Inside the eventDidMount callback, I create another object of type EventInput using the values from the EventImpl object that is supplied in the callback. I remove the supplied event from the calendar and add this event using the calendar object.

    onEventMounted(mountArg: EventMountArg) { //method called by eventDidMount
      let event: EventImpl = mountArg.event;
      if (
        !event.extendedProps['alreadyModified'] &&
        event.extendedProps['type'] === 'break_event'
      ) {
        let eventInput: EventInput = {
          title: event.title,
          start: event.startStr,
          end: event.endStr,
          backgroundColor: '#FDDD9F',
          borderColor: '#202020',
          textColor: '#202020',
          extendedProps: {
            ...event.extendedProps,
            alreadyModified: true,
          },
        };
        mountArg.event.remove();
        this.calendarComponent
          .getApi()
          .addEvent(eventInput, 'example-event-source');
      }
    }
    

    To access the calendar object in order to add the event, make following changes:- In your template, use localreference (like #calendar) in the full-calendar element like

    <full-calendar #calendar [options]="calendarOptions"></full-calendar>
    

    and your component.ts, add:-

    @ViewChild('calendar') calendarComponent!: FullCalendarComponent;
    

    use the calendar object like this (inside eventDidMount):-

    this.calendarComponent.getApi().addEvent(eventInput, 'example-event-source');
    

    IMPORTANT NOTE 1:-

    When providing event source, make sure you give an id to the event source like this:-

    eventSources: [
      {
        id: 'example-event-source',
        events: (a: any, success: any, c: any) =>
          this.initDummyEvents(a, success, c),
      },
    ]
    

    and when adding the event in evenDidMount, provide this id in the Calendar::addEvent method:-

    this.calendarComponent.getApi().addEvent(eventInput, 'example-event-source');
    

    It is important, otherwise, the calendar will not refresh this event when the source is refreshed and you will end up getting an extra event, each time the dates are loaded.

    IMPORTANT NOTE 2:-

    Make sure when you create the new event input, if necessary, provide a flag that can be used to distinguish that this event is already modified. Otherwise whenever we will add this event, eventDidMount will trigger for it, and remove this event and add it again, triggering another eventDidMount, resulting in an infinite loop. So to deal with this, add an extendedProp during modified input event creation:-

    let eventInput: EventInput = {
      //.. other properties
      extendedProps: {
        //..
        alreadyModified: true,
    }
    

    use this extendedProp inside eventDidMount to distinguish the modified events and don't perform the removal/addition action for them:-

    onEventMounted(mountArg: EventMountArg) {// method called by eventDidMount
      let event: EventImpl = mountArg.event;
      if (!event.extendedProps['alreadyModified']) { // only called if event is not already modified
      //.. your modification/remove/addition logic
      }
    }