Search code examples
javascriptangularjswatchdigest

AngularJS $watch - infinite loop


I have 2 dates - start and end. I want to validate (and re-assure) start < end. I get notified they change using $watch, and then I go and change them to make sure the condition is met, but then it triggers the $watch again and then I get max iterations reached of $digest and things go south.

So my question is - how do I change variables that are watched inside the $watch without causing infinite cycle. (Aside from having more checks and changing them only if they actually needs to be changed)

Here is my code:

var TIME_DAY = 1000 * 60 * 60 * 24;

$scope.$watch('timeFilter.startTime', function () {
    validateStartEndTime();
},true);

$scope.$watch('timeFilter.endTime', function () {
    validateStartEndTime();
},true);

function validateStartEndTime() {
    if ($scope.timeFilter.startTime != null && $scope.timeFilter.endTime != null) {
        // Remove all seconds
        if ($scope.timeFilter.startTime.getSeconds() > 0)
            $scope.timeFilter.startTime.setSeconds(0);
        if ($scope.timeFilter.endTime.getSeconds() > 0)
            $scope.timeFilter.endTime.setSeconds(0);
        if ($scope.timeRange.minTime.getSeconds() > 0)
            $scope.timeRange.minTime.setSeconds(0);
        if ($scope.timeRange.maxTime.getSeconds() > 0)
            $scope.timeRange.maxTime.setSeconds(0);

        // When start time isn't before end time
        if ($scope.timeFilter.startTime >= $scope.timeFilter.endTime) {
            // We want to increase end time by 1 minute, if possible - do it. Else - reduce start time by 1 minute
            if ($scope.timeFilter.endTime < $scope.timeRange.maxTime) {
                $scope.timeFilter.endTime.setMinutes($scope.timeFilter.startTime.getMinutes() + 1);
            } else {
                $scope.timeFilter.startTime.setMinutes($scope.timeFilter.startTime.getMinutes() - 1);
            }
        }

        // Start time before min time
        if ($scope.timeFilter.startTime < $scope.timeRange.minTime) {
            $scope.timeFilter.startTime = $scope.timeRange.minTime;
            if ($scope.timeFilter.endTime <= $scope.timeFilter.startTime) {
                $scope.timeFilter.endTime = $scope.timeFilter.startTime;
                $scope.timeFilter.endTime.setMinutes($scope.timeFilter.endTime.getMinutes() + 1);
            }
        }

        // End time after max time
        if ($scope.timeFilter.endTime > $scope.timeRange.maxTime) {
            $scope.timeFilter.endTime = $scope.timeRange.maxTime;
            if ($scope.timeFilter.startTime >= $scope.timeFilter.endTime) {
                $scope.timeFilter.startTime = $scope.timeFilter.endTime;
                $scope.timeFilter.startTime.setMinutes($scope.timeFilter.startTime.getMinutes() - 1);
            }
        }
    }
}

Solution

  • Honestly I would prefer to find a way to avoid to change a variable that is watched constantly. But, if you really want, you could deregister your $watch function when you have to change your variables being watched and then re-register again the $watch function.

    To deregister a $watch function you just need to get the reference and invoke when you need.

    var myWatch = $scope.$watch('timeFilter.startTime', function () {
       validateStartEndTime();
    },true);
    myWatch(); // It will deregister it.
    

    Bear in mind that sometimes when you first watch a variable, it will trigger automatically the function. My suggestion is to always make the check if the old variable is really changed.

    var myWatch = $scope.$watch('timeFilter.startTime', function (newValue, oldValue) {
       if(newValue === oldValue){
          return;
       }
       validateStartEndTime();
    },true);
    

    I hope it works for you. Unfortunately I cannot test this code right now.