AngularJS - ngClick, custom directive and isolated scope issue

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 as follows:

 <button ng-click="doIt()" spinner="spinIt">Spin It</button> 

When the spinner value (i.e., the value of $scope.spinIt in this example) is true , the element should be hidden and spinnerButton should be displayed spinnerButton . If spinner is false , the element must be visible and spinnerButton must be hidden.

The problem here is that doIt() not in an isolated scope, so it doesn't click on a click.

What will be the Angular method for implementing this directive?

+6
source share
7 answers

My suggestion is to see what happens to these spinners. Be more API oriented .

The relevant part follows. We use a regular callback to indicate when we are done, so the spinner knows the reset 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 you expect to use $q . It will work better with Angular-style asynchronous operations and will exclude callback functions instead of having a reset counter when fulfilling a promise.

+8
source

Here is a polished version of the directive that I got into (at the suggestion of Yuuki), if 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 this:

 <button class="btn btn-primary" spinner-click="createNewTask()" spinner-text="Creating..."> Create </button> 

Controller 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 
+2
source

Yes, it will call doIt in your isolated area.

You can use $ parent.doIt in this case

 <button ng-click="$parent.doIt()" spinner="spinIt">Spin It</button> 
+1
source

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 attr is not specified, the attribute name is assumed to match the local name. The defined and visible scope definition is {localFn: '& myAttr'}, then the isolate scope property localFn will point to the function wrapper for the expression count = count + value. It is often desirable to transfer data from an isolated area through an expression to the parent area, this can be done by passing a map of local variable names and values ​​to the shell of the fn expression. For example, if the expression is an increment (quantity), we can specify the value of the sum by calling localFn as localFn ({amount: 22}).

so inlclude doIt: "&doIt" in the scope declaration, then you can use doIt as a function in an isolated scope.

+1
source

I am confused why you are not compiling everything in the directive, as if it were a standalone module. This is at least what I will do. In other words, you have an HTML click handler, some behavior in the directive, and some behavior in the external controller. This makes your code much less portable and decentralized.

In any case, you may have reasons for this that are not separate, but my suggestion would be to link all the “Spin It” related to it in the spinner directive. This means that the click handler, the doIt() function and the template material are inside the link function.

This way you don’t have to worry about code sharing and obfuscation. Or, did I just miss something?

0
source

I don’t know about the “angular” method, but I suggest not using an isolated scope, but instead just creating a child scope. Then run attrs.$observe to get any properties needed for your directive.

those.

 app.directive('spinner', function() { return { restrict: 'A', scope: true, //Create child scope not isolated scope 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); //Using attrs.$observe attrs.$observe('spinner', function(showSpinner) { spinnerButton.toggle(showSpinner); element.toggle(!showSpinner); }); } }; }); 

I find this method better than using '$ parent' to avoid isolated scope in other directives (e.g. ngClick or ngModel), since the end user of your directive should not know if your directive requires or not use '$ parent' or not in core angularjs directives.

0
source

Using CoffeeScript and the FontAwesome icon.

  • No need to manually specify spinning text
  • It will simply add the contents of the spinner to the left of the text when loading
  • We must use it finally instead , and then for a promise, otherwise the counter will remain there if it fails?
  • I have to use $ compilation because the contents of the button are dynamically compiled as I use https://github.com/angular-translate/angular-translate

 app.directive 'spinnerClick', ["$compile", ($compile) -> restrict: 'A' link: (scope, element, attrs) -> originalHTML = element.html() spinnerHTML = "<i class='fa fa-refresh fa-spin'></i> " element.click -> return if element.is('.disabled') element.html(spinnerHTML + originalHTML).addClass('disabled') $compile(element.contents())(scope) scope.$apply(attrs.spinnerClick).finally -> element.html(originalHTML).removeClass('disabled') $compile(element.contents())(scope) ] 
0
source

Source: https://habr.com/ru/post/945943/


All Articles