Use Tippy.js in Angular
Asked Answered
R

3

8

I have a directive with the following code

import { Directive, Input, OnInit, ElementRef, SimpleChanges, OnChanges } from '@angular/core';
import tippy from 'tippy.js';

@Directive({
  selector: '[tippy]'
})
export class TippyDirective implements OnInit, OnChanges {

  @Input('tippyOptions') public tippyOptions: Object;

  private el: any;
  private tippy: any = null;
  private popper: any = null;

  constructor(el: ElementRef) {
    this.el = el;
  }

  public ngOnInit() {
    this.loadTippy();
  }

  public ngOnChanges(changes: SimpleChanges) {
    if (changes.tippyOptions) {
      this.tippyOptions = changes.tippyOptions.currentValue;
      this.loadTippy();
    }
  }

  public tippyClose() {
    this.loadTippy();
  }

  private loadTippy() {
    setTimeout(() => {
      let el = this.el.nativeElement;
      let tippyOptions = this.tippyOptions || {};

      if (this.tippy) {
        this.tippy.destroyAll(this.popper);
      }

      this.tippy = tippy(el, tippyOptions, true);
      this.popper = this.tippy.getPopperElement(el);
    });
  }
}

And using the directive as follows

<input tippy [tippyOptions]="{
              arrow: true,
              createPopperInstanceOnInit: true
            }" class="search-input" type="text" 
(keyup)="searchInputKeyDown($event)">

How can I have the Tippy shown on mouseenter or focus as these are the default triggers, from the tippy instance I have in the directive, this is what I get when I put console.log(this.tippy) on line 44

{
  destroyAll:ƒ destroyAll()
  options:{placement: "top", livePlacement: true, trigger: "mouseenter focus", animation: "shift-away", html: false, …}
  selector:input.search-input
  tooltips:[]
}

As I am getting an error when I try to use

this.popper = this.tippy.getPopperElement(el);

ERROR TypeError: _this.tippy.getPopperElement is not a function

How can I get this directive to work as I took it from a repo in github

https://github.com/tdanielcox/ngx-tippy/blob/master/lib/tippy.directive.ts

What is it that I am missing here, any help is appreciated, thanks

Rosebud answered 10/8, 2018 at 14:10 Comment(0)
G
11

I'm not sure what they were trying to accomplish in the linked repo you have included. To get tippy.js to work though, you should be able to change the directive to the following:

import { Directive, Input, OnInit, ElementRef } from '@angular/core';
import tippy from 'tippy.js';

@Directive({
  /* tslint:disable-next-line */
  selector: '[tippy]'
})
export class TippyDirective implements OnInit {

  @Input('tippyOptions') public tippyOptions: Object;

  constructor(private el: ElementRef) {
    this.el = el;
  }

  public ngOnInit() {
    tippy(this.el.nativeElement, this.tippyOptions || {}, true);
  }
}

Working example repo

Gaynell answered 10/8, 2018 at 19:34 Comment(4)
Hey.. okay let me try thatRosebud
Hey @peinearydevelopment, Man!!! They say never give up, I can't believe this! I have been trying to get it to work the whole day, I even went out to grab some cold drinks just to cool down, now I came back and now this transpires!, thanks so much, I really appreciate your help :DRosebud
Pleasure, glad it helped you. Looking in the tippy.js file, there wasn't anything in there called getPopperElement, so I'm not entirely sure what they were trying to do with that.Gaynell
@gotnull Refer to the referenced repo, specifically github.com/PdUi/stack-overflow-answer-repos/blob/master/… line 27Gaynell
A
7

This works with tippy.js 6.x

@Directive({selector: '[tooltip],[tooltipOptions]'})
export class TooltipDirective implements OnDestroy, AfterViewInit, OnChanges {
  constructor(private readonly el: ElementRef) {}

  private instance: Instance<Props> = null;

  @Input() tooltip: string;
  @Input() tooltipOptions: Partial<Props>;

  ngAfterViewInit() {
    this.instance = tippy(this.el.nativeElement as Element, {});
    this.updateProps({
      ...(this.tooltipOptions ?? {}),
      content: this.tooltip,
    });
  }

  ngOnDestroy() {
    this.instance?.destroy();
    this.instance = null;
  }

  ngOnChanges(changes: SimpleChanges) {
    let props = {
      ...(this.tooltipOptions ?? {}),
      content: this.tooltip,
    };

    if (changes.tooltipOptions) {
      props = {...(changes.tooltipOptions.currentValue ?? {}), content: this.tooltip};
    }
    if (changes.tooltip) {
      props.content = changes.tooltip.currentValue;
    }

    this.updateProps(props);
  }

  private updateProps(props: Partial<Props>) {
    if (this.instance && !jsonEqual<any>(props, this.instance.props)) {
      this.instance.setProps(this.normalizeOptions(props));
      if (!props.content) {
        this.instance.disable();
      } else {
        this.instance.enable();
      }
    }
  }

  private normalizeOptions = (props: Partial<Props>): Partial<Props> => ({
    ...(props || {}),
    duration: props?.duration ?? [50, 50],
  });
}

Using this looks like:

<button [tooltip]="'Hello!'">Hover here</button>
<button [tooltip]="'Hi!'" [tooltipOptions]="{placement: 'left'}">Hover here</button>
Agone answered 7/6, 2021 at 17:38 Comment(3)
Nice directive @alex-rempel . Would you mind explaining what jsonEqual is because I am not familiar with it?Calcar
jsonEqual is any implementation where you compare items on JSON level e.g. for typescript something like: ts export const jsonEqual = <T>(aa: T, bb: T): boolean => JSON.stringify(aa) === JSON.stringify(bb); Agone
Underrated answer.Theocrasy
B
1

You can also use the lifecyle hook ngAfterViewInit then you don't need the setTimeout.

public ngAfterViewInit() {
    this.loadTippy();
}
Barneybarnhart answered 25/4, 2020 at 17:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.