Search code examples
angularprimengviewchildangular-decoratorprimeng-calendar

Why @ViewChild() decorator is not working in this Angular component using PrimeNG FullCalendar?


I am finding the following problem working on a simple Angular 9 project that uses PrimeNG FullCalendar component, this one: https://primefaces.org/primeng/showcase/#/fullcalendar

This PrimeNG FullCalendar component is based on this other Angular original component https://fullcalendar.io/docs

WHAT I HAVE TO DO: I have to implement an external draggable events as done here: https://stackblitz.com/edit/fullcalendar-angular-demo?file=src%2Fapp%2Fapp.component.html

In the previous example it is used the original Fullcalendar.io component and not the PrimeNG one, but being PrimeNG a wrapping of this component it should be possibile.

WHAT I HAVE DONE: I created a project containing the calendar and handling simple event as click on a specific date, click on a specific event, moove an event from a date to another date in my calendar. Here the repository of the code implemented by me untill now: https://bitbucket.org/dgs_poste_team/soc_calendar/src/master/

WHAT IS MY PROBLEM: As you can see into my fullcalendar.component.hml file I have this code:

<p>fullcalendar works!</p>

<div class="content-section implementation">
  <p-fullCalendar #fullcalendar
                  [events]="events"
                  [options]="options">
  </p-fullCalendar>
</div>

<div #external>
  <p>
    <strong>Draggable Events</strong>
  </p>
  <div class='fc-event'>My Event 1</div>
  <div class='fc-event'>My Event 2</div>
  <div class='fc-event'>My Event 3</div>
  <div class='fc-event'>My Event 4</div>
  <div class='fc-event'>My Event 5</div>
</div>

So the p-fullCalendar is the PrimeNG component rendering my calendard and it is referenced by #fullcalendar.

Then I have a div referenced by #external containing the external events that I need to drag into my calendar.

This external events drag operation is not working.

The problems seems to depend by the fact that the typescript component code can't access to this div #external reference.

Into my fullcalendar.component.ts file I have defined this class implementing the logic behind my component (I am implementing it following the provided example):

import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { EventService } from '../event.service';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin, { Draggable } from '@fullcalendar/interaction';
import { FullCalendar } from 'primeng';

@Component({
  selector: 'app-fullcalendar',
  templateUrl: './fullcalendar.component.html',
  styleUrls: ['./fullcalendar.component.css']
})
export class FullcalendarComponent implements OnInit {

  events: any[];
  options: any;
  header: any;

  @ViewChild('fullcalendar') fullcalendar: FullCalendar;
  @ViewChild('external') external: ElementRef;

  constructor(private eventService: EventService) {}

  ngOnInit() {
    this.eventService.getEvents().then(events => { this.events = events;});
    //this.eventService.getEvents().then(res => {console.log(res)} )
    console.log("bla");
    //console.log(this.events.length);

    this.options = {
        plugins:[ dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin ],
        defaultDate: '2017-02-01',
        header: {
            left: 'prev,next',
            center: 'title',
            right: 'dayGridMonth,timeGridWeek,timeGridDay'
        },
        editable: true,

        dateClick: (dateClickEvent) =>  {         // <-- add the callback here as one of the properties of `options`
          console.log("DATE CLICKED !!!");
        },

        eventClick: (eventClickEvent) => {
          console.log("EVENT CLICKED !!!");
        },

        eventDragStop: (eventDragStopEvent) => {
          console.log("EVENT DRAG STOP !!!");
        }
    };


    console.log("external: " + this.external);
    console.log(this.fullcalendar.getCalendar().getEvents);

    new Draggable(this.external.nativeElement, {
      itemSelector: '.fc-event',
      eventData: function(eventEl) {
        return {
          title: eventEl.innerText
        };
      }
    });

  }

}

As you can see I have these two properties decorated by @ViewChild

@ViewChild('fullcalendar') fullcalendar: FullCalendar;
@ViewChild('external') external: ElementRef;

I think that for some reason that I can't understand these two lines are not correctly working because in my console I obtain the following error on this line:

new Draggable(this.external.nativeElement, {
    ................................................
    ................................................
    ................................................
});

This is the error when it try to access to this.external.nativeElement property:

core.js:6272 ERROR TypeError: Cannot read property 'getCalendar' of undefined
    at FullcalendarComponent.ngOnInit (fullcalendar.component.ts:56)
    at callHook (core.js:4770)
    at callHooks (core.js:4734)
    at executeInitAndCheckHooks (core.js:4674)
    at refreshView (core.js:11998)
    at refreshComponent (core.js:13461)
    at refreshChildComponents (core.js:11701)
    at refreshView (core.js:12036)
    at renderComponentOrTemplate (core.js:12114)
    at tickRootContext (core.js:13668)

As you can see in my code I also added this line to debug pourpose (into the ngOnInit() method):

console.log("external: " + this.external);

and the console output is:

external: undefined

so it seems to me that it can't access to the #external div reference.

Is my assumption correct? What is wrong? What am I missing? How can I try to fix this bug?


Solution

  • It doesn't work because ngOnInit is too early for ViewChild, there's a special hook for it AfterViewInit, all ViewChild have been initialised when it's triggered and you can access this.external.nativeElement for example.

    Here you can find an example how they use it: https://angular.io/guide/lifecycle-hooks#responding-to-view-changes

    export class AfterViewComponent implements  AfterViewInit {
      @ViewChild('external') external: ElementRef;
    
      ngAfterViewInit() {
        consoloe.log(this.external.nativeElement); // works
      }
    }