2

What i try to achieve is to extend ngbPopover directive and wrap in custom directive all this proeprties so instead of showing them just to include my directive.

For example i am using ngbpopover like this:

<button
    type="button"
    class="btn btn-primary mb-4"
    placement="right-top"
    [ngbPopover]="popOver"
    popoverClass="popover-custom"
    #popOverRef="ngbPopover"
    triggers="manual"
    [autoClose]="false"
    (click)="popOverRef.toggle()"
    (hidden)="onPopoverClose(options)"
>
    Standard Picker
</button>
<ng-template #popOver>
    <popover [data]="options" [popover]="popOverRef"><popover>
</ng-template>

My goal at the end is to have something like:

<button type="button" class="btn btn-primary mb-4" customDirective></button>

so all this props to be handled inside customDirective. Tried to extend ngbPopoverDirective as code below but i struggle to show popover with this approach. Is this doable or some other idea?

Custom directive:

import {
    Directive,
    ElementRef,
    Renderer2,
    Injector,
    ComponentFactoryResolver,
    ViewContainerRef,
    NgZone,
    Inject,
    ChangeDetectorRef,
    ApplicationRef,
    Input,
    OnInit,
} from '@angular/core';
import { NgbPopover, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap';
import { DOCUMENT } from '@angular/common';

@Directive({
    selector: '[popover]'
})
export class PopoverDirective extends NgbPopover implements OnInit {
    @Input()
    popover: string;
    config: NgbPopoverConfig = {
        autoClose: false,
        placement: 'right-top',
        triggers: "manual",
        container: 'body',
        disablePopover: false,
        popoverClass: 'popover-custom',
        openDelay: 0,
        closeDelay: 0
    }

    constructor(
        _elementRef: ElementRef<HTMLElement>,
        _renderer: Renderer2,
        injector: Injector,
        componentFactoryResolver: ComponentFactoryResolver,
        viewContainerRef: ViewContainerRef,
        config: NgbPopoverConfig,
        _ngZone: NgZone,
        @Inject(DOCUMENT) _document: any,
        _changeDetector: ChangeDetectorRef,
        applicationRef: ApplicationRef
    ) {
        super(
            _elementRef,
            _renderer,
            injector,
            componentFactoryResolver,
            viewContainerRef,
            config,
            _ngZone,
            _document,
            _changeDetector,
            applicationRef
        );
        console.log("here");
    }

    ngOnInit() {
        console.log("onInit")
    }
}
Micko
  • 431
  • 8
  • 27

2 Answers2

1

Ok finally got this to work not sure why my previous code was not working. Anyway i succeed with extending ngbPopover directive. In my case i wanted popoup to be open on parent click for example button, this can be change to hover (mouseenter).... etc..

import {
    ElementRef,
    Directive,
    Input,
    TemplateRef,
    EventEmitter,
    Renderer2,
    Injector,
    ComponentFactoryResolver,
    ViewContainerRef,
    NgZone,
    OnInit,
    OnDestroy,
    Inject,
    ChangeDetectorRef,
    ApplicationRef,
} from '@angular/core';
import { NgbPopover, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap';
import { DOCUMENT } from '@angular/common';

@Directive({
    selector: '[customPopover]',
    exportAs: 'customPopover',
})
export class PopoverDirective extends NgbPopover implements OnInit {
    @Input() customPopover: TemplateRef<any>;

    constructor(
        private _elRef: ElementRef,
        private _render: Renderer2,
        injector: Injector,
        componentFactoryResolver: ComponentFactoryResolver,
        private viewContainerRef: ViewContainerRef,
        config: NgbPopoverConfig,
        ngZone: NgZone,
        private changeRef: ChangeDetectorRef,
        @Inject(DOCUMENT) _document: any,
        applicationRef: ApplicationRef
    ) {
        super(
            _elRef,
            _render,
            injector,
            componentFactoryResolver,
            viewContainerRef,
            config,
            ngZone,
            _document,
            changeRef,
            applicationRef
        );
        this.triggers = 'manual';
        this.popoverTitle = '';
        this.container = 'body';
        this.popoverClass = 'popover-custom';
        this.autoClose = false;
    }

    ngOnInit(): void {
        super.ngOnInit();
        this.ngbPopover = this.customPopover;

        // we need to listen on parent element click envet for example button
        // and toggle popover
        this._render.listen(this._elRef.nativeElement.parentNode, 'click', () => {
            this.toggle();
        });
    }

    ngOnDestroy(): void {
        super.ngOnDestroy();
    }
}

component that use this directive:

<ng-template #popTemplate>
    <custom-content-comp [data]="test" [popover]="popOverRef"></custom-content-comp>
</ng-template>
<span [customPopover]="popTemplate" #popOverRef="customPopover" placement="right-top" triggers="manual">
    <ng-content></ng-content>
</span>

For popTemplate i am using again custom component this can be whatever we want either string either custom template.

Usage:

<div class="col-3">
                <br />
                <button
                    type="button"
                    class="btn btn-primary mb-4"
                    customPopoverComp
                    >
                    Standard Picker
                    </button>
            </div>
Micko
  • 431
  • 8
  • 27
0

Instead of creating a directive, you could also create a component with the selector my-popover.

This component will hold your HTML template code:

<button
    type="button"
    class="btn btn-primary mb-4"
    placement="right-top"
    [ngbPopover]="popOver"
    popoverClass="popover-custom"
    #popOverRef="ngbPopover"
    triggers="manual"
    [autoClose]="false"
    (click)="popOverRef.toggle()"
    (hidden)="onPopoverClose(options)"
>
    Standard Picker
</button>
<ng-template #popOver>
    <popover [data]="options" [popover]="popOverRef"><popover>
</ng-template>

And then finally to use that, you could do the following:

<my-popover></my-popover>

If you need to interact with that component, e.g. to listen to the click events have a look at input and output bidings of components.

Hope this helps!

For more information on the difference between components and directives, you could check this question.

gerstams
  • 415
  • 2
  • 12
  • Hey @gerstams yes at first i just used custom component that holds all this html and it was working good. But thing is that in this way html is 'hardcoded' right? It is always button with popover. That is why i want to have custom directive or either custom attribute component that will just 'append' those properties to the html element (either button or input or list item.. etc... – Micko Apr 03 '20 at 11:23
  • Alright. Got you! maybe this [question helps](https://stackoverflow.com/questions/45299945/ngbpopover-with-html-inside). Otherwise please try to be a bit more precise when describing what you want to do. – gerstams Apr 03 '20 at 12:14
  • this is for adding html as content instead of simple string. I already have that in the example above. It is explained that i want custom directive that will wrap all ng bootstrap popover attributes and i can just call it like: where customDirective will 'append' maybe those popover props. – Micko Apr 03 '20 at 12:30