Use Popper.js in Angular projects

TL;DR Follow to the repository, clone it and explore popper.directive.ts and app.component.html

Prerequisites: you should have basic knowledge of JavaScript, HTML, CSS, Angular 2+ and RxJs.

Topics to be discussed:

I will start with 4th item in the list.

The most important thing to realise when starting using Popper.js at first is that it does not apply any styles and effects. Meaning it will only position your element and not set it any styles or show element only on hover/click etc.

What it does is simply positioning your element and tracking its placement. You can add some features though.

Tooltip example
Tooltip example

So let’s start.

Firstly we create a new Angular app (I will use Angular 8 with SCSS preprocessor)

ng new popper-app

Wait for the dependencies install and open the new project in your favourite code editor or IDE.

Then we need to install Popper.js

npm i popper.js

After Popper.js was added to the project we create a new directive.

ng g d popper

Then we navigate to the created directive.

First we import Popper.js

import Popper from 'popper.js';

We specify some inputs to work with

// The hint to display
@Input() target: HTMLElement;
// Its positioning (check docs for available options)
@Input() placement?: string;
// Optional hint target if you desire using other element than
// specified one
@Input() appPopper?: HTMLElement;

Now we need to implement OnInit and OnDestroy life cycles to create and dispose our tooltip and few properties to keep data.

// The popper instance
private popper: Popper;
private readonly defaultConfig: PopperOptions = {
placement: 'top',
removeOnDestroy: true
};
constructor(private readonly el: ElementRef) {}ngOnInit(): void {
// An element to position the hint relative to
const reference = this.appPopper ? this.appPopper : this.el.nativeElement;
this.popper = new Popper(reference, this.target, this.defaultConfig);
}
ngOnDestroy(): void {
if (!this.popper) {
return;
}

this.popper.destroy();
}

Here we create and dispose popper following directive’s life cycle to avoid memory leaks.

Now it’s time to create a markup for popper and try it out.

For that we navigate to the app.component.html and paste following (you are free to replace the default boilerplate with content)

<ng-container [appPopper]="target" [target]="tooltip"></ng-container><button #target>This is a long button but it's worth your hovering</button><span #tooltip>Hooray!</span>

Then you need to run ng serve in a command line and open http://localhost:4200/

Now you should see next result

Intermediate result

Here we witness the work of Popper.js. The hint is being centred relative to the hint target.

You may notice that I’ve specified placement: "top" whilst the hint is at the bottom. It’s the expected behaviour, because Popper.js moves an element to another side if it has no space (the button is at the very top and there is no space to show the hint at the top so it was moved to the bottom). You can configure this behaviour if you don’t want this behaviour (feel free to explore the docs for available options).

This is time for styling!

These are the styles.scss (global styles)

body {
height: 200vh;
}
button {
margin-top: 100px;
}
.popper {
display: inline-block;
background: #eee;
border-radius: 3px;
padding: 10px;
&[x-placement^="bottom"] {
margin-top: 15px;
}
&[x-placement^="top"] {
margin-bottom: 15px;
}
&__arrow {
width: 0;
height: 0;
padding: 0;
margin: 0;
position: absolute;
border: {
style: solid;
width: 10px;
color: transparent;
}
}
&[x-placement^="top"] &__arrow {
border-bottom-width: 0;
border-top-color: #eee;
bottom: -10px;
}
&[x-placement^="bottom"] &__arrow {
border-top-width: 0;
border-bottom-color: #eee;
top: -10px;
}
}

This is app.component.html now

<ng-container [appPopper]="target" [target]="tooltip"></ng-container><button #target>This is a long button but it's worth your hovering</button><span class="popper" #tooltip>
<span class="popper__arrow"></span>
Hooray!
</span>

And popper.directive.ts (the changed part)

private readonly defaultConfig: PopperOptions = {
placement: "top",
removeOnDestroy: true,
modifiers: {
arrow: {
element: ".popper__arrow"
}
}
};

Here in arrow.element I specify the selector to use for arrow (it has to be inline/inline-block element).

Now it’s time to save the changes and check the page after build.

Styled caption

Now you’ve done a great job and created your own hint directive which can be applied to elements.

Note: you don’t have to use ng-container or any other extra element for it to work. I used this case to show that it doesn’t matter where to apply the directive.

You can do a markup even that simple

<button appPopper [target]="tooltip">This is a long button but it's worth your hovering</button><span class="popper" #tooltip>
<span class="popper__arrow"></span>
Hooray!
</span>

Check the result :)

Now the most interesting thing comes to us — showing the hint on hover.

Now you need to inject Renderer2 in constructor in order to apply styles/css in better and safer syntax than vanilla JS classList.add .

constructor(
...
private readonly renderer: Renderer2) {}

Add a method to directive

private mouseHoverHandler(e: string): void {
if (e === "mouseenter") {
this.renderer.removeStyle(this.target, "display");
this.popper.enableEventListeners();
this.popper.scheduleUpdate();

} else {
this.renderer.setStyle(this.target, "display", "none");
this.popper.disableEventListeners();
}
}

And call it from ngOnInit

ngOnInit() {
...

this.renderer.setStyle(this.target, "display", "none");
merge(
fromEvent(reference, "mouseenter"),
fromEvent(reference, "mouseleave")
).pipe(
filter(() => this.popper != null),
pluck("type")
).subscribe((e: any) => this.mouseHoverHandler(e));
}

Now after you save you should see a hint only when hover the button.

For anyone who needs to see the code here is the repo

A front-end developer who uses Angular as a main framework and loves it