如何以编程方式将 ngBootstrap 弹出窗口添加到元素?

how to programmatically add ngBootstrap popover to element?

我目前正在我的 angular v8 应用程序中处理日历。

这是我使用的插件:https://fullcalendar.io

这是我在 html 模板中包含的组件:

    <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>

但是如何向元素添加 ngBootstrap 弹出窗口或工具提示?

这是 renderTooltip():

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

我会创建一个简单的组件,它基本上只是一个弹出窗口包装器:

@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 属性 将从我们的主要组件传递,因此我们可以创建我们想要的任何模板
  • 我们还持有 NgbPopover 个实例,因此我们可以稍后使用 popover.open(context)

还要确保您已将此组件添加到 NgModule:

entryComponents 数组中
@NgModule({
  imports:      [ BrowserModule, FullCalendarModule, NgbPopoverModule ],
  declarations: [ AppComponent, PopoverWrapperComponent ],
  entryComponents: [PopoverWrapperComponent],
                              \/
                           like this
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

现在我们要将此组件动态渲染到 $event.el 元素中,并在 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();
    }
  }
}

这里的关键部分是我们如何渲染组件 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)
}

动态呈现的组件与 Angular 变化检测树无关,因此我们必须将其视图添加到 ApplicationRef 视图,以便变化检测应该在那里工作。

确保您已在模板中订阅以下活动:

(eventRender)="renderTooltip($event)"
(eventDestroy)="destroyTooltip($event)"
(eventMouseEnter)="showPopover($event)"
(eventMouseLeave)="hidePopover($event)"

您还应该为弹出窗口定义模板,即:

<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