Search code examples
angularjsangularjs-directiveangularjs-scope

Angular Directive: Button Spinner


I have created a directive, its behave like when you click on button it will show spinner or loader, its show user there is something in progress like API call or moving to another page, I am facing following issues:

  1. If more than one directive used on page, its show spinner for both button instead clicked button, i want it should show only for clicked button

  2. In some scenarios, clicked event bind three times, as there are three span used inside button to show spinner, there is some issue with event propagation

Here is directive code:

(function () {
    'use strict';

    angular
        .module('app.base')
        .directive('buttonSpinner', directiveFunction)
        .controller('btnController', ControllerFunction);


    // ----- directiveFunction -----
    directiveFunction.$inject = [];

    /* @ngInject */
    function directiveFunction() {

        var directive = {
            restrict: 'E',
            scope: {
              label: "@",
              available: "="
            },
            controller: 'btnController',
            controllerAs: 'vm',
            replace: true,
            transclude: true,
            template:
              '<button ng-disabled="vm.isDisabled">'
                + '<span ng-hide="vm.isClicked">{{label}}</span>'
                + '<div class="spinner" ng-show="vm.isClicked">'
                +   '<span class="bounce1"></span>'
                +   '<span class="bounce2"></span>'
                +   '<span class="bounce3"></span>'
                + '</div>' +
              '</button>'
        };

        return directive;
    }

    // ----- ControllerFunction -----
    ControllerFunction.$inject = [ '$scope' ];

    /* @ngInject */
    function ControllerFunction( $scope ) {

        var vm = this;
        vm.isClicked = false;
        $scope.$on('APICALLED', function(event, data){
          vm.isClicked = data.done;
          if( data.elem ) {
            angular.element(document.getElementById(data.elem))[0].disabled = true;
          } else {
            vm.isDisabled = data.disable;
          }
        });
    }

})();

How to use:

In View:

<button-spinner class="btn btn-primary btn-block btn-lg" type="submit" ng-click="vm.notifyMe($event)" label="Notify Me" available="vm.notify.is" id="notifyMeBtn"></button-spinner>

In Controller:

to show spinner:

$rootScope.$broadcast('APICALLED', {'done': true, 'disable': true});

to hide spinner:

 $rootScope.$broadcast('APICALLED', {'done': false, 'disable': false});

Solution

  • Your problem is related to your $broadcast's so its a pattern problem. $rootScope.$broadcast() isnt a good solution at all. e.g.) You need to destroy $rootScope.$broadcast.$on() bindings manually. Just parse a unique scope var into the directive like loading. This scope param could be handled by the controller itself for each loading procedure:

    /* @ngInject */
    function directiveFunction() {
    
        var directive = {
            restrict: 'E',
            scope: {
                label: "@",
                available: "=",
                loading: "="
            },
            controller: 'btnController',
            controllerAs: 'vm',
            replace: true,
            transclude: true,
            template:
            '<button ng-disabled="vm.isDisabled">'
            + '<span ng-hide="vm.isClicked">{{label}}</span>'
            + '<div class="spinner" ng-show="vm.loading">'
            +   '<span class="bounce1"></span>'
            +   '<span class="bounce2"></span>'
            +   '<span class="bounce3"></span>'
            + '</div>' +
            '</button>'
        };
    
        return directive;
    }
    

    View

    <button-spinner class="btn btn-primary btn-block btn-lg"
                    type="submit"
                    ng-click="vm.notifyMe($event)"
                    loading="vm.isLoading"
                    label="Notify Me"
                    available="vm.notify.is"
                    id="notifyMeBtn"></button-spinner>