8
\$\begingroup\$

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

Template:

<div [@visibilityChanged]="isVisible" *ngIf="shouldShow" [ngClass]="messageType">
    <ng-content></ng-content>
</div>

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', () => {
      setTimeout(() => {
        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:

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

Tell me what do you think about it!

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.