Angular 2 Spinner Component

As I posted the little Input Debounce Component some days ago, here is a very little Angular 2 Spinner Component. The component uses a spinner from Tobias Ahlin’s SpinKit. For completeness, I’ll post the CSS and HTML code for it here, too.

Component Code

CSS

.spinner {
  width: 40px;
  height: 40px;

  position: relative;
  margin: 100px auto;
}

.double-bounce1, .double-bounce2 {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-color: #333;
  opacity: 0.6;
  position: absolute;
  top: 0;
  left: 0;

  -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
  animation: sk-bounce 2.0s infinite ease-in-out;
}

.double-bounce2 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

@-webkit-keyframes sk-bounce {
  0%, 100% { -webkit-transform: scale(0.0) }
  50% { -webkit-transform: scale(1.0) }
}

@keyframes sk-bounce {
  0%, 100% {
    transform: scale(0.0);
    -webkit-transform: scale(0.0);
  } 50% {
      transform: scale(1.0);
      -webkit-transform: scale(1.0);
    }
}

From Tobias Ahlin’s SpinKit

HTML

<div [hidden]="!isDelayedRunning" class="spinner">
    <div class="double-bounce1"></div>
    <div class="double-bounce2"></div>
</div>

Angular 2 Component

'use strict';

import {Component, Input, OnDestroy} from 'angular2/core';

@Component({
    selector: 'my-spinner',
    templateUrl: 'app/components/spinner/spinner.html'
})
export class SpinnerComponent implements OnDestroy {
    private currentTimeout: number;
    private isDelayedRunning: boolean = false;

    @Input()
    public delay: number = 300;

    @Input()
    public set isRunning(value: boolean) {
        if (!value) {
            this.cancelTimeout();
            this.isDelayedRunning = false;
            return;
        }

        if (this.currentTimeout) {
            return;
        }

        this.currentTimeout = setTimeout(() => {
            this.isDelayedRunning = value;
            this.cancelTimeout();
        }, this.delay);
    }

    private cancelTimeout(): void {
        clearTimeout(this.currentTimeout);
        this.currentTimeout = undefined;
    }

    ngOnDestroy(): any {
        this.cancelTimeout();
    }
}

At first the necessary imports are done. Then a new component is created with an external template file. The contents of that file is the HTML code as shown above. The class SpinnerComponent  has two private fields:

  • currentTimeout : This is the identifier for the set timeout, so it can be cancelled.
  • isDelayedRunning : This is the internal state of the spinner, if it is running or not.

And it has a public input field delay representing the milliseconds to wait, before showing the spinner. A second input isRunning  is defined a setter, so we can execute some code, if the value gets set:

At first, it checks, if the actual value  is false . If so, it cancels the timeout and immediately switches isDelayedRunning  to false  which leads to hiding the spinner right away. We don’t want to use setTimeout  when hiding the spinner, since it would not make any sense. The spinner would be shown even the data has been loaded already.

The other case, value  is set to true , it first checks if a timeout has already been set to not start a new one. If not, we create a timeout with the given delay . When it times out, isDelayedRunning  will be set to true , showing the spinner.

I also implementedOnDestroy  to clean up any timeout, which could still be active, when the spinner gets destroyed.

Usage

Imagine we’re doing a HTTP request, where the spinner should show, when the requests takes a little more time:

'use strict';

import {Component} from 'angular2/core';

import {SpinnerComponent} from '../spinner/spinner';
import {ApiService} from '../../services/apiService';

@Component({
    selector: 'my-sample-view',
    directives: [SpinnerComponent],
    template: '<my-spinner [isRunning]="isRequesting"></my-spinner>',
})
export class SampleViewComponent {
    public isRequesting: boolean;
    public items: Array<any>;

    constructor(private apiService: ApiService) {
        this.refresh();
    }

    public refresh(): void {
        this.isRequesting = true;
        this.apiService.sampleHttpGetRequest()
            .subscribe(
                result => this.items = result, 
                () => this.stopRefreshing(),
                () => this.stopRefreshing());
    }

    private stopRefreshing() {
        this.isRequesting = false;
    }
}

Same here: Do the necessary imports at first. We need the SpinnerComponent  and some imaginary ApiService , which will be a representative for doing a HTTP GET request. We define a new component SampleViewComponent , using the SpinnerComponent  and binding its isRunning  input to a variable called isRequesting . When ever isRequesting  changes, it will update the state of the spinner. Within the refresh() -method, isRequesting  is set to true , creating the internal timeout. If the apiService.sampleHttpGetRequests()  takes longer than 300 msec, the spinner will show up. After the Observable is done (or got an error), we use stopRefreshing()  to set isRequesting  to false  again, which will hide the spinner.