how to programmatically add ngBootstrap popover to element?

拈花ヽ惹草 提交于 2019-12-20 02:28:08

问题


I am currently workingon 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 to an element?

this is the renderTooltip():

renderTooltip(event) {
    // bind ngBootstrap tooltip or popover to $event.el
}

Hope someone can help me out render the popover or tooltip to the events on the calendar.


回答1:


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 want
  • we also hold of NgbPopover 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



来源:https://stackoverflow.com/questions/57278854/how-to-programmatically-add-ngbootstrap-popover-to-element

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!