Romain Linsolas
Romain Linsolas

Reputation: 81627

Why does my Angular function is called in infinite loop?

In my application, I have an input field, and if the user enters a specific string (basically a string that matches a regular expression), a div is displayed.

The (simplified) HTML part:

<div ng-app>
  <h2>Todo</h2>
  <div ng-controller="TodoCtrl">
      <input type="text" ng-model="searchText"/>
      <div>Hello</div>
      <div ng-show="isValid(searchText)">World !</div>
  </div>
</div>

and my controller :

function TodoCtrl($scope) {
    var reg = /20\d{2}/g;    
    $scope.isValid = function(str) {
        console.log('Is valid?');
        return reg.test(str);
    }
}

When the user enters a year (thus validated by the regular expression), I get the following error:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["isValid(searchText); newVal: true; oldVal: false"],["isValid(searchText); newVal: false; oldVal: true"],["isValid(searchText); newVal: true; oldVal: false"],["isValid(searchText); newVal: false; oldVal: true"],["isValid(searchText); newVal: true; oldVal: false"]]
http://errors.angularjs.org/1.2.1/$rootScope/infdig?p0=10&p1=%5B%5B%22isVal…2isValid(searchText)%3B%20newVal%3A%20true%3B%20oldVal%3A%20false%22%5D%5D
    at http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:78:12
    at Scope.$digest (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:11472:19)
    at Scope.$apply (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:11682:24)
    at HTMLInputElement.listener (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:15653:13)
    at http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:2562:10
    at Array.forEach (native)
    at forEach (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:300:11)
    at HTMLInputElement.eventHandler (http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js:2561:5) 

Here is a link to a JsFiddle: http://jsfiddle.net/U3pVM/6598/

What is wrong with my code, and how to enhance it?

Upvotes: 4

Views: 1205

Answers (3)

Yuliam Chandra
Yuliam Chandra

Reputation: 14640

Try move the reg variable inside the function.

function TodoCtrl($scope) {  
    $scope.isValid = function(str) {
        var reg = /20\d{2}/g;
        console.log('Is valid?');
        return reg.test(str);
    }
}

As pointed in the link by Karolis Juodelė, the regex stores the last valid position and the g symbol causes the second call to be false. By moving the variable locally inside the function, the reg variable is a new reference if you still want to use the g symbol.

Angular evaluate the function twice to make sure that it doesn't change, but indeed in the second call, it returns false, and then true, and then false, so forth.

Upvotes: 0

Mosho
Mosho

Reputation: 7078

You should use $watch and a scope variable, instead of using the return value of a function to decide whether to show the element.

What happens is that this line:

reg.test(str)

Makes a change in str which is a reference to the scope variable, which triggers another digest, causing an infinite loop. Edit: this is due to the regex having the global flag, as mentioned in another answer. So that combined with how angular's digest cycle works is causing it.

Here's the solution using $watch:

function TodoCtrl($scope) {
    var reg = /20\d{2}/g;    
    var isValid = function(str) {
        $scope.show = reg.test(str);
    }
    $scope.$watch('searchText',isValid);

}

fiddle

Upvotes: 1

Karolis Juodelė
Karolis Juodelė

Reputation: 3770

It's the g at the end of reg. When you test the same expression on the same string twise, the second time it searches after what was found the first time. So, if you start with "2001.", the function will return true, then, called again, search the remaining "." and return false.

Here is another answer, explaining how it works.

Upvotes: 7

Related Questions