[FIXED] Add Buttons to Fullcalendar Events

Issue

I want to add buttons to my Fullcalendar events.
For example: Delete, Snap to prev. Event, Snap to following Event.
Is there an out of the box possibility for me to do so?

If not, how can I use the eventRender functionality of FullCalendar to achieve this?
I can add html to the event, but it is not a p-button, nor does the onClick Event trigger. How can I add html and use the primeng elements and events?

eventRender(info: any) {
        info.el.querySelector('.fc-title').innerHTML += '<p-button (onClick)="delete(info.event)" icon="pi pi-trash"></p-button>';
      }

I am using Fullcalendar v4 with primeng.

Solution

We can use dynamic creation of an Angular component when it comes to 3 third-party integration where we have to touch DOM.

First of all, let’s create our desired template:

@Component({
  template: `<p-button (onClick)="deleted.emit(info.event)" icon="pi pi-trash"></p-button>`,
})
export class EventActionsComponent {
  info: any;
  @Output() deleted = new EventEmitter();
}

It’s a component which will be dynamically created and loaded into FullCalendar event template.

In order to achieve that we need to use low-level Angular API which involves ComponentFactoryResolver and ViewContainerRef:

your-host.component.ts

export class AppComponent {
  // store all created ComponentRefs so that they can be successfully destroyed in eventDestroy 
  ngComponentsMap = new Map<HTMLElement, ComponentRef<EventActionsComponent>>();

  // get componentFactory of dynamic component only once
  eventActionsComponentFactory = this.resolver.resolveComponentFactory(
    EventActionsComponent
  );

  constructor(
    private resolver: ComponentFactoryResolver,
    private vcRef: ViewContainerRef
  ) {}

  ngOnInit(): void {
    this.options = {
      ...
      eventRender: (info: any) => {
        // use Promise here to avoid ExpressionChangedAfterItHasBeenCheckedError
        Promise.resolve().then(() => {
          const compRef = this.vcRef.createComponent(
            this.eventActionsComponentFactory
          );
          this.ngComponentsMap.set(info.el, compRef);
          const targetEl = info.el.querySelector('.fc-title');
          targetEl.innerHTML = '';
          // pass anything you want here
          compRef.instance.info = info;
          // handle all desired outputs here
          compRef.instance.deleted.subscribe((event) => {
            alert('Do smth with delete');
          });
          // port created ng component to the right destination
          targetEl.appendChild(compRef.location.nativeElement);
        });
      },
      eventDestroy: (info: any) => {
        // destroy ng component and clear ngComponentsMap
        const compRef = this.ngComponentsMap.get(info.el);
        if (compRef) {
          compRef.destroy();
          this.ngComponentsMap.delete(info.el);
        }
      },
    };
  }

  ...
}

You can read through my comments to get basic understanding of what is going on here.

Forked Stackblitz Example

Answered By – yurzui

Answer Checked By – Mary Flores (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published