I am finding some problem trying to use @ViewChild() decorator.
I will try to explain in details what I am trying to do.
I have a correctly working version of my project (at the moment it is just a minimal prototype on which I am working on), in case you can see the complete code here: https://bitbucket.org/dgs_poste_team/soc_calendar/src/master/
So basically I have this main app-component.html main view:
<div class="container">
<div class="row">
<div class="col-12">
<app-header></app-header>
</div>
<div class="row">
<div class="col-4">
<p>DRAGGABLE</p>
</div>
<div class="col-8">
<app-fullcalendar></app-fullcalendar>
</div>
</div>
</div>
As you can see it contains the reference to the app-fullcalendar component. This component contain a calendar implementing something like this: https://fullcalendar.io/docs/external-dragging-demo
Where is shown a calendar and an "external events" box where I can pick an event and drag into inside my calendar.
In my original version it works fine but at the moment I implemented it in the following simple way:
1) This is my fullcalendar.component.html view file:
<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>
<app-people-list></app-people-list>
</div>
As you can see it contains a div having reference name set to external and this div contain a sub component of my app-fullcalendar component that simply renders the event list (app-people-list, in my use case an event is a person that I have to put on a specific date of my calendar, but this is not so important now).
Then into the typescript code handling the app-fullcalendar component behavior I have this code:
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;
//people: any[];
@ViewChild('fullcalendar') fullcalendar: FullCalendar;
@ViewChild('external') external: ElementRef;
constructor(private eventService: EventService) {}
ngOnInit() {
this.eventService.getEvents().then(events => { this.events = events;});
//this.eventService.getPeople().then(people => {this.people = people;});
//console.log("PEOPLE: " + this.people);
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 !!!");
},
eventReceive: (eventReceiveEvent) => {
console.log("EXTERNAL EVENT LAND ON THE CALENDAR");
//console.log(eventReceiveEvent);
this.eventService.addEvent(eventReceiveEvent);
}
};
}
ngAfterViewInit() {
//console.log("ngAfterViewInit() START !!!")
new Draggable(this.external.nativeElement, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
console.log("DRAG !!!");
return {
title: eventEl.innerText
};
}
});
}
}
So in this code I take the reference to the div having reference named external, by this line:
@ViewChild('external') external: ElementRef;
So this external properties contains the reference of this specific div that will contains my list of events\people that I have to drag inside my calendar.
Then I use this reference property inside the ngAfterViewInit() to allow the drag and drop of the element shown inside the div having refernce external.
ngAfterViewInit() { //console.log("ngAfterViewInit() START !!!")
new Draggable(this.external.nativeElement, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
console.log("DRAG !!!");
return {
title: eventEl.innerText
};
}
NOTE: the .fc-class specified in the item selector is rendered inside the and it is put on each element of the event\person list rendered.
Perfect it works fine !!!
Now my problem: my idea is that in this way the code is too strongly coupled and I was thingkin to decouple it in this way:
1) The div referenced my #external and containing my rendering the list of people\event that have to be dragged inside my calendar is no more a sub component of my full-calendar component but it is at the same level inside my app-component.
So basically I remove it from my fullcalendar.component.html that become:
<p>fullcalendar works!</p>
<div class="content-section implementation">
<p-fullCalendar #fullcalendar
[events]="events"
[options]="options">
</p-fullCalendar>
</div>
<!-- REMOVED -->
<!--
<div #external>
<p>
<strong>Draggable Events</strong>
</p>
<app-people-list></app-people-list>
</div>
-->
And I put it in the left column of my app.component.html file, that now is:
Ok now the list of people\events is rendered on the left side but I can't anymore drag the event\person from this list into my calendar. This because in the JavaScript console I am obtaining this error message:
core.js:6272 ERROR TypeError: Cannot read property 'nativeElement' of undefined
at FullcalendarComponent.ngAfterViewInit (fullcalendar.component.ts:67)
at callHook (core.js:4770)
at callHooks (core.js:4734)
at executeInitAndCheckHooks (core.js:4674)
at refreshView (core.js:12060)
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)
This error happen into the ngAfterViewInit() function of the fullcalendar.app.component.ts file:
ngAfterViewInit() { //console.log("ngAfterViewInit() START !!!")
new Draggable(this.external.nativeElement, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
console.log("DRAG !!!");
return {
title: eventEl.innerText
};
}
});
It seems that this component can't retrieve anymore the div with the #external reference when this hook is executed. I suspect that this happens because this hook function is automatically executed after that Angular have completly rendered the fullcalendar.app.component component.
In the first working example it worked because the app-people-list was a sub component so this function execution happened after that all the subcomponent was rendered. But now my app-people-list is no more a subcomponent and I suspect that this ngAfterViewInit() is no more the right solution. Is it this idea correct?
What can I do to solve the situation and have my example correctly working? Have I to use another hook or there is some other good solution?
ViewChild
ONLY works on things declared in that component's template, not the entire app's template.
but you can pass template refs as inputs no problem...
<app-fullcalendar [externals]="externals"></app-fullcalendar>
and you can just declare externals
as an input in full calendar instead of a view child:
@Input() externals: ElementRef
which you can use as you see fit in ngOnInit
or later hooks.
however, if I'm reading this correctly, that seems not needed, and all you need to do is move this part:
@ViewChild('external') external: ElementRef;
ngAfterViewInit() {
//console.log("ngAfterViewInit() START !!!")
new Draggable(this.external.nativeElement, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
console.log("DRAG !!!");
return {
title: eventEl.innerText
};
}
});
}
out of fullcalendar.component.ts
, and into the app.component.ts
controller. since all you're doing with the view child is declaring it as draggable, this should work fine in app component and you don't really need to access it inside full calendar.