Consider the following directive: (Live Demo)
app.directive('spinner', function() {
return {
restrict: 'A',
scope: {
spinner: '=',
doIt: "&doIt"
},
link: function(scope, element, attrs) {
var spinnerButton = angular.element("<button class='btn disabled'><i class='icon-refresh icon-spin'></i> Doing...</button>");
element.after(spinnerButton);
scope.$watch('spinner', function(showSpinner) {
spinnerButton.toggle(showSpinner);
element.toggle(!showSpinner);
});
}
};
});
which is used like this:
<button ng-click="doIt()" spinner="spinIt">Spin It</button>
When spinner
's value (i.e. the value of $scope.spinIt
in this example) is true
, the element should be hidden and spinnerButton
should appear instead. When spinner
's value is false
, the element should be visible and spinnerButton
should be hidden.
The problem here is that doIt()
is not in the isolated scope, thus not called on click.
What would be the "Angular way" to implement this directive?
My suggestion is to look at what's going on with these spinners. Be a little more API focused.
Relevant part follows. We use a regular callback to indicate when we're done, so the spinner knows to reset the state of the button.
function SpinDemoCtrl($scope, $timeout, $q) {
$scope.spinIt = false;
$scope.longCycle = function(complete) {
$timeout(function() {
complete();
}, 3000);
};
$scope.shortCycle = function(complete) {
$timeout(function() {
complete();
}, 1000);
};
}
app.directive('spinnerClick', function() {
return {
restrict: 'A',
scope: {
spinnerClick: "=",
},
link: function(scope, element, attrs) {
var spinnerButton = angular.element("<button class='btn disabled'><i class='icon-refresh icon-spin'></i> Doing...</button>").hide();
element.after(spinnerButton);
element.click(function() {
spinnerButton.show();
element.hide();
scope.spinnerClick(function() {
spinnerButton.hide();
element.show();
});
});
}
};
});
Here's one that expects use of $q
. It'll work better with Angular-style asynchronous operations, and eliminates the callback functions by instead having the spinner reset on fulfilment of the promise.
Here is the polished version of the directive I ended up with (based on Yuki's suggestion), in case it helps someone: (CoffeeScript)
app.directive 'spinnerClick', ->
restrict: 'A'
link: (scope, element, attrs) ->
originalHTML = element.html()
spinnerHTML = "<i class='icon-refresh icon-spin'></i> #{attrs.spinnerText}"
element.click ->
return if element.is('.disabled')
element.html(spinnerHTML).addClass('disabled')
scope.$apply(attrs.spinnerClick).then ->
element.html(originalHTML).removeClass('disabled')
Use it like so:
<button class="btn btn-primary" spinner-click="createNewTask()"
spinner-text="Creating...">
Create
</button>
Controller's code:
TasksNewCtrl = ($scope, $location, $q, Task) ->
$scope.createNewTask = ->
deferred = $q.defer()
Task.save $scope.task, ->
$location.path "/tasks"
, (error) ->
// Handle errors here and then:
deferred.resolve()
deferred.promise
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With