Как программно добавить всплывающее окно ngBootstrap к элементу?

В настоящее время я работаю над календарем в своем приложении 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
}

🤔 А знаете ли вы, что...
JavaScript имеет множество библиотек и фреймворков, таких как jQuery, Angular, и Vue.js.


6
3 126
1

Ответ:

Решено

Я бы создал один простой компонент, который в основном представляет собой просто всплывающую оболочку:

@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) позже.

Также убедитесь, что вы добавили этот компонент в массив entryComponents вашего NgModule:

@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();
    }
  }
}

Ключевой частью здесь является то, как мы визуализируем компонент динамически с проецируемыми узлами:

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

Пример Stackblitz с @fullcalendar/angular/5.5.0