2

Below is a very simple TODO app build with angularjs.

It works fine so far, but my problem is that angularjs keeps on calling the 'remaining' function on evry keystroke in the input field!! is there anything wrong with the below code?

<!doctype html>
<html ng-app>

<head>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
    <link href="style.css" rel="stylesheet">
</head>

<body>
    <div>
        <h2>TODO:</h2>
        <div ng-controller="TodoCtrl">
            <span>{{remaining()}} of {{todos.length}} remaining</span> [ <a href="" ng-click="archive()">archive</a> ]
            <ul class="list-unstyled">
                <li ng-repeat="todo in todos">
                    <input type="checkbox" ng-model="todo.done" >
                    <span>{{todo.text}}</span>
                </li>
            </ul>
            <form ng-submit="addTodo()">
                <input type="text" ng-model="todo" placeholder="Enter your task here">
            <form>
        </div>
    </div>

    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular.min.js"></script>
    <!--script src="app.js"></script-->
    <script>
    function TodoCtrl($scope) {
        $scope.todos = [
            {text: 'learn angular', done:true},
            {text: 'build an angular app', done:false}
        ];

        $scope.remaining = function() {
            console.log('calling remaining()');
            var count = 0;
            angular.forEach($scope.todos, function(todo) {
                count += todo.done? 0:1;
            });

            return count;
        };

        $scope.addTodo =  function() {
            if($scope.todo)  {
                $scope.todos.push({text: $scope.todo, done:false});
            }
            $scope.todo = '';
        };
    }
    </script>
</body>
</html>
3
  • 2
    Nothing wrong here, it calls remaining() on every digest cycle. and $digest cycle fired on any input change Commented Jan 6, 2014 at 13:44
  • every time that you change the model that is binding to the input, angular will call the $disgest cicle to update the models and bindings. You have binding the todos variable and also the remaining function. It is normal this function to be called in this case. Commented Jan 6, 2014 at 13:48
  • possible duplicate of Expression evaluated 2 times Commented Jan 6, 2014 at 14:06

2 Answers 2

4

This is standard angular behavior. On any change to the model or any other binding or angular event it will execute all watches that are setup on the scope. This is called a digest cycle and is usually triggered by a $scope.$apply(). This is why it is extremely important to not do any heavy calculation on functions that are called from a binding expression in the view.

Performance problems happen usually when having functions that do some calculation or filtering on a long list. If this is an issue, the solution is to setup a watch on the collection and update the calculated property as a separat variable in the scope only when the collection changes. In your example, this would prevent recalculating the remaining items in cases where unrelated inputs change.

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

Comments

4

To supplement @dtabuenc's answer, here is an example of how prevent that function firing on every minor change:

Replace:

<span>{{remaining()}} of {{todos.length}} remaining</span> [ <a href="" ng-click="archive()">archive</a> ]

With:

<span>{{remaining}} of {{todos.length}} remaining</span> [ <a href="" ng-click="archive()">archive</a> ]

And replace the definition of $scope.remaining = function()... with:

$scope.$watch('todos', function(todos) {
  var count = 0;
  angular.forEach(todos, function(todo) {
    count += todo.done ? 0 : 1;
  }
  $scope.remaining = count;
}

This will prevent angular from evaluating the remaining() angular expression in your view for every scope change.

1 Comment

You'll probably want to use $watchCollection instead of a standard $watch

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.