Bootstrap, AngularJS “Waiting” Button Directive

Spending more time in the server-side, “back-end” realm of web development, I love it when front-end tasks are made easy through approachable frameworks.  Which is why I like Bootstrap and AngularJS.  I know they both have their flaws and detractors but from my point-of-view, they serve a purpose and make some of the mundane FE tasks much easier.

There’s one element that’s present in virtually every web application: the submit button.  And in most cases, they are expected to perform an important, complex task that involves some handshaking between the client’s browser and the server, which likely also involves a database or other third-parties.  Since that round trip will likely take more than a millisecond, we want to make sure a couple things for optimal user experience:

  • Acknowledge the user clicked the button so they know their action was received.
  • Disable the button to prevent redundant or conflicting submissions.
  • Give some indication that “the wheels are in motion” and that the user should wait patiently.

There is some overlap between these but the best solution involves both visual cues as well as functional changes to the UI.  AngularJS already offers a ng-disabled directive for elements that support that concept.  My most common approach is to bind this to a simple boolean value in my $scope.

$scope.submitting = false;
$scope.submit = function () {
    if ($scope.someForm.$valid) {
        $scope.submitting = true;
        ...
    }
}

The accompanying markup would like this:

<button class="btn btn-primary" ng-disabled="submitting">Submit</button>

Bootstrap already grays out disabled buttons in addition to the browser preventing additional mouse clicks so this would probably do the job. But if we want to go the extra mile, some additional eye-candy might be necessary. What if we tried to incorporate a spinning icon, often called a “throbber”. Even better if we can do it with pure CSS and font glyphs, not animated GIFs. Doing a Google search brought me to a StackOverflow thread. The meaningful snippet is as follows:

.glyphicon.spinning {
    animation: spin 1s infinite linear;
    -webkit-animation: spin2 1s infinite linear;
}

@keyframes spin {
    from { transform: scale(1) rotate(0deg); }
    to { transform: scale(1) rotate(360deg); }
}

@-webkit-keyframes spin2 {
    from { -webkit-transform: rotate(0deg); }
    to { -webkit-transform: rotate(360deg); }
}

If I combined that with some glyph from Bootstrap and/or FontAwesome, you get a pretty nice result.

Almost every web application is going to have several submit buttons that could benefit from this treatment which means we’ll want to make something reusable. In the AngularJS world, that often means a custom directive. Here’s the code:

app.directive('submitButton', function () {
    return {
        transclude: true,
        scope: {
            waiting: '=',
            waitingText: '@',
            type: '@',
            class: '@'
        },
        controller: function ($scope) {
            if (angular.isUndefined($scope.type)) {
                $scope.type = 'submit';
            }
            if (angular.isUndefined($scope.class)) {
                $scope.class = 'btn btn-primary';
            }
        },
        template: '<button type="{{type}}" class="{{class}}" ng-disabled="waiting"><span ng-show="waiting"><i class="fa fa-circle-o-notch spinning"></i>&nbsp;{{waitingText}}</span><span ng-transclude ng-hide="waiting"></span></button>'
    }
})

If you’re not already familiar, the items described in the “scope” attribute define inputs for this directive which normally take the form of attributes in the markup. ‘@’ means the input will be assumed as plain-text. I also defined a “controller” function which allows me to check the parameters “type” and “class” and enforce defaults for each. The “waiting” parameter is a two-way binding which is expected to a be either a boolean variable or function in the outer scope that controls when the button is in “waiting mode”. It also expects a “waitingText” which is what replaces the normal text while waiting. You can see I chose to use the “circle-o-notch” icon from FontAwesome but any icon that looks good spinning would work. Actually using the directive in markup would look something like this:

<submit-button waiting="submitting" waiting-text="Submitting...">Submit</submit-button>

The “submitting” set in the waiting attribute maps to my “$scope.submitting” from the previous code.

Conclusion

I often struggle to come up with situations where an AngularJS directive is the best solution. Normally, I can find ways in which markup generated by server-side code does the job better. However, seems like a good example where the directive makes sense and has the advantage of working on any platform.

Leave a Reply