Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 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?

like image 456
Misha Moroshko Avatar asked May 27 '13 14:05

Misha Moroshko


2 Answers

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.

like image 138
Asherah Avatar answered Sep 22 '22 15:09

Asherah


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
like image 30
Misha Moroshko Avatar answered Sep 22 '22 15:09

Misha Moroshko