Creating an Angular Component Library - Overlay Loader Component


Another simple component we can build for our library is a overlay loader component. This is a component you might use when first loading a page or loading new data for a page and want to disable the entire screen while the page/data is loading.

A full screen demo of what we’re going to build.

 

Prerequisites

 

Generate Our Overlay Loader

First we will generate a new module and a component for our page loader.

ng generate module components/overlay-loader --project=foo
ng generate component components/overlay-loader --project=foo

And we will update our module to export our page-loader component to make the component accessable by impoting modules.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OverlayLoaderComponent } from './overlay-loader.component';

@NgModule({
  declarations: [
    OverlayLoaderComponent
  ],
  imports: [
    CommonModule
  ],
  exports: [
    OverlayLoaderComponent
  ]
})
export class OverlayLoaderModule { }

We will also create two enums to model the overlay style and the spinner size.

touch  projects/foo/src/lib/components/overlay-loader/overlay-loader-style.enum.ts
touch  projects/foo/src/lib/components/overlay-loader/spinner-size.enum.ts

We going to create quite a few different overlay styles. Our enum will look something like this…

export enum OverlayLoaderStyle {
  TRANSPARENT = 'overlay-loader-style-transparent',
  DIM_DARK = 'overlay-loader-style-dim-dark',
  DIM_LIGHT = 'overlay-loader-style-dim-light',
  PRIMARY = 'overlay-loader-style-primary',
  SECONDARY = 'overlay-loader-style-secondary',
  SUCCESS = 'overlay-loader-style-success',
  INFO = 'overlay-loader-style-info',
  WARNING = 'overlay-loader-style-warning',
  DANGER = 'overlay-loader-style-danger'
}

And our spinenr size enum…

export enum SpinnerSize {
  SMALL = 'spinner-size-sm',
  MEDIUM = 'spinner-size-md',
  LARGE = 'spinner-size-lg'
}

Now we can update our components directory’s public_api.ts file…

// Overlay Loader
export * from './overlay-loader/overlay-loader.component';
export * from './overlay-loader/overlay-loader.module';
export * from './overlay-loader/spinner-size.enum';
export * from './overlay-loader/overlay-loader-style.enum';

 

Creating Our Overlay Loader

Our overylay loader component will be made up of an overlay div fixed across the full width and height of the screen. Inside we will also have a circular spinner indicating that the page is loading. Its a pretty simple HTML structure and will look something like this…

<div class="overlay-loader">
  <div class="overlay" [@fadeInOut] *ngIf="isLoading" [ngClass]="overlayStyle">
    <div class="spinner">
      <div class="spinner-inner" [ngClass]="spinnerSize"></div>
    </div>
  </div>
  <div class="content" [@fadeInOut] *ngIf="!isLoading">
    <ng-content></ng-content>
  </div>
</div>

Our SCCS styles…

@import '../_shared/scss/variables';

@mixin generate-spinner-by-size($size, $borderWidth) {
  border-width: $borderWidth;
  width: $size;
  height: $size;
}

@mixin generate-overlay-style($color, $spinner, $track) {
  background: $color;_
  .spinner {
    .spinner-inner{
      border-color: $track;
      border-top-color: $spinner;
    }
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.overlay-loader {
  position: relative;

  .overlay {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0; 
    right: 0;
    z-index: 100;

    .spinner {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -80%);
      
      .spinner-inner {
        border-radius: 50%;
        border-style: solid;
        
        animation: spin 0.75s linear infinite;
      
        &.spinner-size-sm {
          @include generate-spinner-by-size(72px, 9px);
        }

        &.spinner-size-md {
          @include generate-spinner-by-size(96px, 12px);
        }

        &.spinner-size-lg {
          @include generate-spinner-by-size(128px, 16px);
        }
      }
    }

    &.overlay-loader-style-transparent {
      background: transparent;
      .spinner {
        .spinner-inner{
          border-color: #FFF;
          border-top-color: #2b3e50;
        }
      }
    }
    &.overlay-loader-style-dim-dark {
      @include generate-overlay-style(
        rgba(#000, 0.3), $primary, darken(#FFF, 20%));
    }
    &.overlay-loader-style-dim-light {
      @include generate-overlay-style(
        rgba(#FFF, 0.5), $primary, lighten($primary, 20%));
    }
    
    &.overlay-loader-style-primary {
      @include generate-overlay-style(
        $primary, darken($primary, 10%), lighten($primary, 20%));
    }

    &.overlay-loader-style-secondary {
      @include generate-overlay-style(
        $secondary, darken($secondary, 10%), lighten($secondary, 20%));
    }

    &.overlay-loader-style-success {
      @include generate-overlay-style(
        $success, darken($success, 10%), lighten($success, 20%));
    }

    &.overlay-loader-style-info {
      @include generate-overlay-style(
        $info, darken($info, 10%), lighten($info, 20%));
    }

    &.overlay-loader-style-warning {
      @include generate-overlay-style(
        $warning, darken($warning, 10%), lighten($warning, 20%));
    }

    &.overlay-loader-style-danger {
      @include generate-overlay-style(
        $danger, darken($danger, 10%), lighten($danger, 20%));
    }    
  }
}

And our component class file which has a few @Input decorators for configuration values…

import { Component, OnInit, Input } from '@angular/core';
import { SpinnerSize } from './spinner-size.enum';
import { OverlayLoaderStyle } from './overlay-loader-style.enum';
import { FADE_IN_OUT } from '../_shared/animations/fade-in-out.animation';

@Component({
  selector: 'foo-overlay-loader',
  templateUrl: './overlay-loader.component.html',
  styleUrls: ['./overlay-loader.component.scss'],
  animations: [FADE_IN_OUT]
})
export class OverlayLoaderComponent implements OnInit {
  @Input()
  public isLoading: boolean;

  @Input()
  public overlayStyle: OverlayLoaderStyle;

  @Input()
  public spinnerSize: SpinnerSize;

  constructor() {
    this.spinnerSize = SpinnerSize.MEDIUM;
    this.overlayStyle = OverlayLoaderStyle.DIM_DARK;
    this.isLoading = false;
  }

  ngOnInit(): void {
  }
}

One thing you will notice about our component class file is we using another shared animation, FADE_IN_OUT, which we still need to create. It’s a basic fade in/out animation that we will probably use elsewhere in our library. We should add this to our _shared/animations directory for reuse later.

First we need to create our file…

touch projects/foo/src/lib/components/_shared/animations/fade-in-out.animation.ts

And our fade in/out animation…

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

export const FADE_IN_OUT = trigger('fadeInOut', [
  transition(':enter', [
    style({ opacity: 0 }),
    animate('0.1s', style({ opacity: 1 }))
  ]),
  transition(':leave', [
    style({ opacity: 1 }),
    animate('0.1s', style({ opacity: 0 }))
  ])
])

 

Using Our Overlay Loader Component

Using our overlay loader component is pretty straight forward. We can wrap our page content in an <foo-overlay-loader> element passing in any configuration values we want.

<foo-overlay-loader 
    [overlayStyle]="OverlayLoaderStyle.DIM_DARK"
    [spinnerSize]="SpinnerSize.SMALL"
    [isVisible]="isVisible">

  <!-- ADDITIONAL HTML PAGE CONTENT HERE -->

</foo-overlay-loader>

Changing the isVisible value will toggle the loader on and off.

Now we can build our library with npm run build.

 

Stackblitz Result

I’ve also embeded a Stackblitz showing our overlay loader being used with our dashabord layout we create a few posts back. As you navigate to different pages using the side navigation, you will see a our overlay loader being display prior to displaying the page. A setTimeout is used to simulate loading data.

A full screen demo can also be viewed here

 

 

The completed github repository can be found here

 


comments powered by Disqus