Todd Palmer
Todd Palmer

Reputation: 1102

AngularJS Directive doesn't update scope for callback

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.

Upvotes: 3

Views: 906

Answers (1)

Gabriel Hobold
Gabriel Hobold

Reputation: 693

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

Upvotes: 3

Related Questions