2

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.

1
  • pls share the fiddle or plunker demo to better see the problem Commented Jun 6, 2013 at 19:12

2 Answers 2

6

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 :)

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

6 Comments

$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.
Also, that blog post on Javascript timeouts has very little to do with why $timeout(...) may work.
@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 =)
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.
I've just come across this problem myself. I added the scope.$apply() call before firing the event. It does fix the problem, but also produces a javascript error in the console: Error: [$rootScope:inprog] $apply already in progress
|
2

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.

3 Comments

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.
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.
@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);

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.