2

I am $watching an object for changes and setting $scope.changed = true, however there are circumstances where i want to set it to false right after I've altered the data programatically:

$scope.$watch('data', function() {
    $scope.changed = true;
}, true);

function loadData(data) {
    $scope.data = data;

    // I want to run $scope.$apply() here so the $watch is triggered
    // before changed is set back to false
    $scope.$apply();

    $scope.changed = false;
}

// in DOM:
<div ng-click="loadData(newData)">

If I run loadData manually using a non-angular event it works fine (of course $applying the scope again afterwards), but if I run it from something like the above ngClick then it errors out with "$apply already in progress".

Using a $timeout works in most circumstances, but there are some places where I really want it to happen synchronously.

function loadData(data) {
    $scope.data = data;
    // what I want to avoid:
    $timeout(function() {
        $scope.changed = false;
    })
}

Is it possible to apply scope changes synchronously, or am I doing change handling wrong?

Thanks.

3 Answers 3

3

If you're not doing something really special you can use angular.equals to check if two objects are equal. Use that in combination with angular.copy so you have a copy of the original data.

$scope.isDirty = function () {
    return !angular.equals(initialData, $scope.data);
}

Plunker

Doing it this way you don't need to worry about the order of your $watch functions and the code will be much easier to understand.

Performance wise, this might be heavier though. You could optimize it by changing so that isDirty only is changed when the data is changed.

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

1 Comment

Thanks, works well and after fiddling with it a bit the overhead shouldn't be too bad. I modified it a so it doesn't need to run the comparison after the data has changed the first time since changed was reset. Thankfully I don't need to know if it returns to its original state. Plnkr
0

ngclick will occupy the $apply handler, so you can't run process which need $apply in an event handler function,just $timeout all action in a function like :

function loadData(data){
    $timeout(function(){
        $scope.data = data;
        $scope.$apply();
        $scope.changed = false;
    });
}

Try this, but not tested...

3 Comments

I'd like to do it all synchronously if possible, setting a $timeout is my current workaround but working with asynchronous operations tends to add complexity.
@DeathCarrot I think function(){#code#} and function(){$timeout(function(){#code#})} is nearly the same, except running before or after the endpoint of click event.You need to ensure the synchronism of #code# and endpoint of click event?
In some cases the equivalent of the loadData function is far down the call stack, the original call of which can be called from several places, some of which are likely deep in someone else's code. There can also be other timeouts which depend on the value of changed being valid, which it won't be between the $watch and $timeout. Of course you can wrap the code that uses changed in two $timeouts but that eventually starts getting a bit tedious.
0

You can use a second $scope variable to keep track of whether the change came from a particular function and based on that, enable the $scope.changed variable assignment in your $watch.

Add a variable to serve as a marker in your loadData() function. In this case, we'll call it $scope.explicitChange:

function loadData(data) {
    $scope.data = data;

    // I want to run $scope.$apply() here so the $watch is triggered
    // before changed is set back to false
    $scope.$apply();

    $scope.changed = false;
    $scope.explicitChange = true;
}

Check if $scope.explicitChange has been set in your $watch:

$scope.$watch('data', function(){
    if(!$scope.explicitChange){
        $scope.changed = true;
    }
    else{
        // Reset $scope.explicitChange
        $scope.explicitChange = false;
    }
}, true);

Comments

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.