AngularJS - ngClick, custom directive, and isolated scope issue
Solution 1
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.
Solution 2
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
Solution 3
Yes, it will call doIt in your isolated scope.
You can use $parent.doIt in that case
<button ng-click="$parent.doIt()" spinner="spinIt">Spin It</button>
Solution 4
From the AngularJS Documentation (http://docs.angularjs.org/guide/directive):
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localFn:'&myAttr' }, then isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression and to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
so inlclude doIt: "&doIt"
in your scope declaration, then you can use doIt as a function in your isolated scope.
Misha Moroshko
I build products that make humans happier. Previously Front End engineer at Facebook. Now, reimagining live experiences at https://muso.live
Updated on June 15, 2022Comments
-
Misha Moroshko about 2 years
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) istrue
, the element should be hidden andspinnerButton
should appear instead. Whenspinner
's value isfalse
, the element should be visible andspinnerButton
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?