3

I am using directives to create a component library in AngularJS 1.5. Hence, my directives need to have isolate scopes.

Some of my directives have callbacks so you can pass in a function to get invoked by the directive. However, when that callback is invoked by the directive, it doesn't seem like the changes to $scope attributes are fully updated like I would expect them to be.

Here is a Plunker that shows this behavior: http://embed.plnkr.co/Rg15FHtHgCDExxOYNwNa/

Here is what the code looks like:

<script>
    var app = angular.module('myApp', []);
    app.controller('Controller', ['$scope',function($scope) {
        // initialize the value to something obvious
        $scope.clickersValue = "BEFORE";

        // when this call back is called we would expect the value to be updated by updated by the directive
        $scope.clickersCallback = function() {
        //$scope.$apply(); // $apply is not allowed here
        $scope.clickersValueRightAfterCall = $scope.clickersValue;
        console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
    };
  }
]);

app.directive('clicker', [function() {
  return {
    restrict: 'EA',
    template: '<div ng-click="clicked()">click me!</div>',
    controller: ['$scope', function($scope) {
      $scope.clicked = function() {
        console.log("you clicked me.");
        $scope.newValue = 'VALID';
        $scope.myUpdate();
      }
    }],
    scope: {
      "newValue": "=",
      "myUpdate": "&"
    }
  };
}]);
</script>

So when clickersCallback gets invoked the clickersValue attribute still has the old value. I have tried using $scope.$apply but of course it isn't allowed when another update is happening. I also tried using controller_bind but got the same effect.

1 Answer 1

3

Wrap the code inside clickersCallback function in a $timeout function.

$timeout(function() {
    $scope.clickersValueRightAfterCall = $scope.clickersValue;
    console.log("clickersCallback: scope.clickersValue", $scope.clickersValue); 
});

Updated plunker

The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle. source

Edit 1: As the OP said below, the user of the directive should not have to write any "special" code in his callback function.

To achieve this behavior I changed the $timeout from de controller to the directive.

Controller callback function (without changes):

$scope.clickersCallback = function() {
    $scope.clickersValueRightAfterCall = $scope.clickersValue;
    console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};

Directive code (inject $timeout in the directive):

 $scope.clicked = function() {
     console.log("you clicked me.");
     $scope.newValue = 'VALID';
     $timeout(function() {
         $scope.myUpdate();
     });
 }

Updated plunker

Sign up to request clarification or add additional context in comments.

3 Comments

Gabriel, Thanks for the creative approach. But, this requires the user of the directive to do something special in their code. I am really looking for a way that the directive user can just use an expression like we do in ng-click. But, I still up-voted your suggestion.
Maybe you can wrap the $scope.myUpdate(); call inside the $timeout in the directive. Is that what you want? plunker
Yes! That is exactly what I wanted! I tried that after your suggestion but I must have gotten it wrong in my code somehow. Can you post that as an answer so I can mark it answered? Can't thank you enough, this is great.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.