Lightweight Angular tooltip component I made a small Popup component to show different error/success...etc. messages.

Template:

&lt;div [@visibilityChanged]="isVisible" *ngIf="shouldShow" [ngClass]="messageType"&gt;
    &lt;ng-content&gt;&lt;/ng-content&gt;
&lt;/div&gt;


Component:

import {
  Component, OnChanges, Input,
  Renderer2, AfterViewInit, ElementRef, AfterViewChecked,
  HostListener
} from '@angular/core';

import { trigger, state, animate, transition, style } from '@angular/animations';

/**
 * This class represents the popup component.
 */
@Component({
  moduleId: module.id,
  selector: 'sd-popup',
  templateUrl: 'popup.component.html',
  styleUrls: ['popup.component.css'],
  animations: [trigger('visibilityChanged', [
    transition(':enter', [
      style({ opacity: 0 }),
      animate(100, style({ opacity: 1 }))
    ]),
    transition(':leave', [
      style({ opacity: 1 }),
      animate(100, style({ opacity: 0 }))
    ])
  ])
  ]
})
export class PopUpComponent implements AfterViewInit, AfterViewChecked {
  @Input() messageType: string | string[];
  @Input() errorState: boolean;
  @Input() parentEl: Element;

  public shouldShow: boolean;

  constructor(private popupEl: ElementRef,
    private renderer: Renderer2) {
  }

  ngAfterViewInit(): void {
    // Append to body for better calculation
    document.body.appendChild(this.popupEl.nativeElement);

    //use random delay for load balancing
    this.renderer.listen(this.parentEl, 'keydown', () =&gt; {
      setTimeout(() =&gt; {
        this.shouldShow = this.errorState;
      }, this.getRandomInt(1, 50));
    });
  }

  @HostListener('window:resize')
  @HostListener('window:scroll')
  ngAfterViewChecked(): void {
    // calculate position when view checked,window is resized or scrolling
    // consider to use load balancing here as well (random delay might be visible)
    this.calculatePosition(this.popupEl.nativeElement, this.parentEl);
  }

  public calculatePosition(element: any, target: any): void {
    const targetOffset = target.getBoundingClientRect();

    this.renderer.setStyle(element, 'top', `${(targetOffset.top + document.documentElement.scrollTop) - element.offsetHeight - 5}px`);
    this.renderer.setStyle(element, 'left', `${targetOffset.left + (target.offsetWidth / 2 - element.offsetWidth / 2)}px`);
    this.renderer.setStyle(element, 'zindex', `${target.zindex + 100}px`);
  }

  getRandomInt(min: number, max: number) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

}


SCSS:

:host {
    position:absolute;
    opacity: 0.9;
 }

 .errorMessage {
    padding: 0.375rem 0.75rem;
    font-size: 1rem;
    line-height: 1.5;
    color: #fff;
    background-image: none;
    background-clip: padding-box;
    border-radius: 0.25rem;
 }

 .warning {
    @extend .errorMessage;
    background-color: #CD464D;
    border: 1px solid #CD464D;
 }

  .success {
     @extend .errorMessage;
     background-color: rgb(15, 230, 147);
     border: 1px solid rgb(15, 230, 147);
  }

 div:after, div:before {
    top: 100%;
    left: 50%;
    border: solid transparent;
    content: " ";
    height: 0;
    width: 0;
    position: absolute;
    pointer-events: none;
}

 div:after {
    border-color: rgba(136, 183, 213, 0);
    border-top-color: inherit;
    border-width: 5px;
    margin-left: -5px;
}
div:before {
    border-color: rgba(194, 225, 245, 0);
    border-top-color: inherit;
    border-width: 6px;
    margin-left: -6px;
}


And finally usage:

&lt;input #passInput type="password" name="password" id="password" class="form-control" formControlName="password"/&gt;
                &lt;sd-popup [parentEl]="passInput" [errorState]="password.errors?.required &amp;&amp; (password.dirty || password.touched)"
                    messageType="success"&gt;Password is required&lt;/sd-popup&gt;


Tell me what do you think about it!
It just looks really good.

I think it stayed this long unanswered because there's not much feedback to give.

I reviewed and liked


The commenting
The naming
The sizes of your functions
Reading flow


I am not convinced that choosing a random int actually helps the load balancing much, but don't have a better alternative. I would also probably put the random number generator in a helper class, it does not really belong in a tooltip component.