I am currently working on a calendar in my angular v8 application.
This is the plugin I use: https://fullcalendar.io
this is the component which I include in my html template:
<full-calendar
defaultView="dayGridMonth"
[editable]="true"
[eventLimit]="5"
[nowIndicator]="true"
[slotLabelFormat]="timeFormat"
[eventTimeFormat]="timeFormat"
[eventClassName]="'fc-event-brand'"
[minTime]="'08:00:00'"
[maxTime]="'24:00:00'"
[header]="{
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth, timeGridWeek, timeGridDay, listWeek'
}"
[plugins]="calendarPlugins"
[events]="calendarEvents"
(eventMouseEnter)="showPopover($event)"
(eventMouseLeave)="hidePopover($event)"
(eventRender)="renderTooltip($event)"></full-calendar>
But how can I add a ngBootstrap popover or tooltip to an element?
this is the renderTooltip():
renderTooltip(event) {
// bind ngBootstrap tooltip or popover to $event.el
}
How To Create a Popover. To create a popover, add the data-toggle="popover" attribute to an element. Note: Popovers must be initialized with jQuery: select the specified element and call the popover() method.
A Bootstrap Popover is an attribute in bootstrap that can be used to make any website look more dynamic. Popovers are generally used to display additional information about any element and are displayed with a click of a mouse pointer over that element.
I would create one simple component which is basically just a popover wrapper:
@Component({
template: `
<div class="fc-content" [ngbPopover]="template" container="body" triggers="manual">
<ng-content></ng-content>
</div>
`,
})
export class PopoverWrapperComponent {
template: TemplateRef<any>;
@ViewChild(NgbPopover, { static: true }) popover: NgbPopover;
}
template
property will be passed from our main component so we can create any template we wantNgbPopover
instance so we can use popover.open(context)
later.Also make sure you've added this component to entryComponents
array of your NgModule
:
@NgModule({
imports: [ BrowserModule, FullCalendarModule, NgbPopoverModule ],
declarations: [ AppComponent, PopoverWrapperComponent ],
entryComponents: [PopoverWrapperComponent],
\/
like this
bootstrap: [ AppComponent ]
})
export class AppModule { }
Now we're going to dynamically render this component into $event.el
element and project child nodes in ng-content
.
import { Component, ComponentRef,
TemplateRef, ViewChild, ComponentFactoryResolver,
Injector, ApplicationRef } from '@angular/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
calendarPlugins = [dayGridPlugin];
calendarEvents = [
{ title: 'event 1', date: '2019-08-09', customProp1: 'customProp1', customProp2: 'customProp2' },
{ title: 'event 2', date: '2019-08-12', customProp1: 'customProp3', customProp2: 'customProp4' }
];
@ViewChild('popoverTmpl', { static: true }) popoverTmpl: TemplateRef<any>;
popoversMap = new Map<any, ComponentRef<PopoverWrapperComponent>>();
popoverFactory = this.resolver.resolveComponentFactory(PopoverWrapperComponent);
constructor(
private resolver: ComponentFactoryResolver,
private injector: Injector,
private appRef: ApplicationRef) {
}
renderTooltip(event) {
const projectableNodes = Array.from(event.el.childNodes)
const compRef = this.popoverFactory.create(this.injector, [projectableNodes], event.el);
compRef.instance.template = this.popoverTmpl;
this.appRef.attachView(compRef.hostView);
this.popoversMap.set(event.el, compRef);
}
destroyTooltip(event) {
const popover = this.popoversMap.get(event.el);
if (popover) {
this.appRef.detachView(popover.hostView);
popover.destroy();
this.popoversMap.delete(event.el);
}
}
showPopover(event) {
const popover = this.popoversMap.get(event.el);
if (popover) {
popover.instance.popover.open({ event: event.event });
}
}
hidePopover(event) {
const popover = this.popoversMap.get(event.el);
if (popover) {
popover.instance.popover.close();
}
}
}
The key part here is how we're rendering component dynamically with projectable nodes:
renderTooltip(event) {
const projectableNodes = Array.from(event.el.childNodes)
const compRef = this.popoverFactory.create(this.injector, [projectableNodes], event.el);
compRef.instance.template = this.popoverTmpl;
this.appRef.attachView(compRef.hostView)
this.popoversMap.set(event.el, compRef)
}
Rendered dynamically component has no relation to the Angular change detection tree so we have to add its view to ApplicationRef
views so change detection should work there.
Make sure you've subscribed to the following event in your template:
(eventRender)="renderTooltip($event)"
(eventDestroy)="destroyTooltip($event)"
(eventMouseEnter)="showPopover($event)"
(eventMouseLeave)="hidePopover($event)"
You should also define template for popover, i.e.:
<ng-template #popoverTmpl let-event="event">
<h6>{{ event.title }}</h6>
<div>
<p>{{ event.extendedProps.customProp1 }}</p>
<p>{{ event.extendedProps.customProp2 }}</p>
</div>
</ng-template>
Stackblitz Example
Stackblitz Example with @fullcalendar/angular/5.5.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With