Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm having a binding timing issue with my AngularJS directive. Its controller looks like this:

controller: function($element, $scope)
{
    $element.find("input").bind("blur", function()
    {
        SendUpdate();
    });

    $scope.Subtract = function()
    {
         Add(-$scope.step);
    }

    $scope.Add = function()
    {
        Add($scope.step);
    }

    function Add(amount)
    {
        $scope.model = parseInt($scope.model) + parseInt(amount);
        console.log("Model: " + $scope.model);
        SendUpdate();
    }

    function SendUpdate()
    {
        $scope.$emit("ProcessUpdate");
    }
}

Everything works properly thus far, and start at a value of 100 and adding 10, it prints out Model: 110, as expected. But, when the event is handled by the parent scope, which is providing the variable that model is bound to, it has not received the updated value by the time the event fires:

$scope.$on("ProcessUpdate", function()
{
    console.log("MyValue: " + $scope.MyValue);
});

That prints out MyValue: 100, even though it's $scope.MyValue that is bound to the directive's model variable (I'm using the "=" binding character, too).

The value is, in fact, being updated. If I press a button that prints out the same exact thing:

console.log("MyValue: " + $scope.MyValue);

It prints the correct value of MyValue: 110. So it's clearly a timing issue. It looks like things are happening in this order:

  1. Update the directive's $scope.model.
  2. Fire the event.
  3. Handle the event.
  4. Update the parent's $scope.model.

What I need is for 4 to happen immediately after 1, because when the event is fired I need the parent scope to be up to date.

Is there a different way that I should be doing this? I don't want to pass the updated value via the event because any number of these directives could be firing and they need to all process the parent scope. I don't want to have to figure out what changed and inject it accordingly. I just want to fire the event after the parent scope has received the directive's new value.

Thank you.

share|improve this question
    
pls share the fiddle or plunker demo to better see the problem –  Ajay Beniwal Jun 6 '13 at 19:12
add comment

2 Answers

up vote 4 down vote accepted

Try the code below

$scope.$on("ProcessUpdate", function() {
    $timeout(function() {
        console.log("MyValue: " + $scope.MyValue);
    });
});

Even though the timeout is zero, it does not execute until interpolation and DOM rendering is complete. That behavior is explained in more detail here:

http://ejohn.org/blog/how-javascript-timers-work/

Hope that works for you :)

share|improve this answer
    
Works perfectly. Thank you! –  Mike Pateras Jun 6 '13 at 22:07
    
$timeout(...) will by default execute within a $scope.$apply() call, which is how interpolation is done. You may not need $timeout(...), all you may need is $scope.$apply(), which is what I said in the comments to this answer. –  rtcherry Jun 6 '13 at 22:50
    
Also, that blog post on Javascript timeouts has very little to do with why $timeout(...) may work. –  rtcherry Jun 6 '13 at 22:52
    
@rtcherry I was curious why you do not think the article is relevant. I am wrong a lot, but I like to find out why so I do not make the same mistake next time =) –  Allan S. Jun 6 '13 at 23:42
    
It does explain timeouts just fine, but $timeout will by default call $scope.$apply(), which is what I think is solving his problem. I am betting if the OP changes it to $timeou(function() {...}, false); he would experience the same problem he had before. –  rtcherry Jun 6 '13 at 23:53
show 2 more comments

The problem is you are using events (which are immediate) and two-way binding uses the Angular $digest loop. How about instead of using $on and $emit you use a $watch function instead?

In your directive, you would do this:

$scope.$watch("MyValue", function(newValue, oldValue) {
  console.log("MyValue: " + $scope.MyValue);
});

And in your controller all you have to do is remove SendUpdate.

share|improve this answer
    
Clever solution, but I'm modifying properties on an object, and I don't think a watch would pick those up. I tried it really quickly and it didn't seem to. –  Mike Pateras Jun 6 '13 at 20:01
1  
How are you modifying them? If it is from outside of the Angular lifecycle you may need to call scope.$apply() from your directive (depending on what is triggering the modification). A sample of your problem would allow us to provide a more complete solution. –  rtcherry Jun 6 '13 at 20:33
    
@MikePateras Also, if you need to watch an object, be sure to pass true in as the third argument to the watch function. $scope.$watch("MyValue", function(newValue, oldValue) { console.log("MyValue: " + $scope.MyValue); }, true); –  rtcherry Jun 7 '13 at 10:52
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.