Search for '{{search_term}}'

Automatic counter using AngularJS

CMOS is the Code-Maven Open Source podcast that also includes video interviews. Subscribe to this feed RSS feed with your Podcast listener app or via iTunes iTunes.

We have seen how to create a counter in AngularJS in which we incremented (or deceremented) the counter by clicking on a button. In this example we'll automatically increased the counter as time passes.

Don't forget to check out the other counter examples!

Schedule future execution using $timeout

examples/angular/automatic_counter.html

<script src="angular.min.js"></script>
<script>
angular.module('CounterApp', [])
    .controller('CounterController', function($scope, $timeout) {
        $scope.counter = 0;
        var updateCounter = function() {
            $scope.counter++;
            $timeout(updateCounter, 1000);
        };
        updateCounter();
    });
</script>
<div ng-app="CounterApp">
   <div ng-controller="CounterController">
   {{counter}}
   </div>
</div>
Try!

In this example the controller function expects two parameters, the $scope that contains the attributes we interact with in our HTML page and$timeout which is a function similar to the plain JavaScript setTimeout function. (Atually this is called dependency injection and not parameters, but let's not worry about that now. Especially as I don't understand it yet.)

$timeout is a function that receives two parameters: a callback function object and a time expressed in millisecond. It schedules the execution of the function object delayed by the time given. Passing 1000 as the second parameter means we want the callback function to run 1 second after this call was made.

Inside the controller function the first thing is that we create an attribute called counter and set the default value of it to 0. We want to start counting from 0.

Then we create a function called updateCounter that, when called, will increment the counter and use $timeout to schedule itself 1 second later as well. Effectively this means that every time updateCounter runs, it increments the counter and asks the system to run it again 1 second later. This means the function will run every 1 second.

Then the last step is to call updateCounter for the first time to initiate the endless loop.

Counter with stop button

this timer will be executed every second, but if we would like to stop it? We'll see an example with an additional button, that will stop the counter.

$timeout returns a promise object we can later use to cancel the timer. In order to have that promise available to us when needed, I've created a variable called timer and assigned the return value of $timeout to it. (Of course I could have used any name here.)

Then I've added a button to the HTML and using ng-click configured it to run then stopCounter method when clicked.

The only thing left was to create the stopCounter method.

At first I created it using var stopCounter = function() { ... } just as the updateCounter, but that did not work. Because we want to call it from the HTML, we need to add this method to the $scope. Hence I had to change the definition to be: $scope.stopCounter = function() { ... }.

Inside we have the statement $timeout.cancel(timer); that will cancel the timer. Go ahead. Try it!

examples/angular/automatic_counter_with_stop.html

<script src="angular.min.js"></script>
<script>
angular.module('CounterApp', [])
    .controller('CounterController', function($scope, $timeout) {
        var timer;
        $scope.counter = 0;
        $scope.stopCounter = function() {
            $timeout.cancel(timer);
        };
        var updateCounter = function() {
            $scope.counter++;
            timer = $timeout(updateCounter, 1000);
        };
        updateCounter();
    });
</script>
<div ng-app="CounterApp">
   <div ng-controller="CounterController">
   {{counter}}
   <button ng-click="stopCounter()">Stop</button>
   </div>
</div>
Try!

Counter with stop and start buttons

As an extra feature I wanted to see how can I add another button, to start the counter again.

The first version was simple. I just added a button and a new function:

$scope.startCounter = function() {
    updateCounter();
};

and the corresponding HTML button to call it.

The problem with this solution was that it allowed me to click the start button several times in a row and the counter started to go much faster and sometimes jumped by 2 or by 3. What actually happened that every time I clicked on the start button, a new timer was created and I had several timers waiting in parallel.

I had to somehow make sure that only one timer exists at any given time. Either by disabling the start button after it was clicked or by checking if there is already a live timer and launching a new one only if there was no live counter.

I picked this solution as I was more interested in the JavaScript/AngularJS solution. I had to make two changes. One in the stopCounter function. I've added

timer = null;

After all once we have cancelled the timer, there is no point in having an unusable object around.

Then in the startCounter I could check if timer is not null and create the new $timeout object only if the timer was null.

examples/angular/automatic_counter_with_stop_start.html

<script src="angular.min.js"></script>
<script>
angular.module('CounterApp', [])
    .controller('CounterController', function($scope, $timeout) {
        var timer;
        $scope.counter = 0;
        $scope.stopCounter = function() {
            $timeout.cancel(timer);
            timer = null;
        };
        $scope.startCounter = function() {
            if (timer === null) {
                updateCounter();
            }
        };
        var updateCounter = function() {
            $scope.counter++;
            timer = $timeout(updateCounter, 1000);
        };
        updateCounter();
    });
</script>
<div ng-app="CounterApp">
   <div ng-controller="CounterController">
   {{counter}}
   <button ng-click="stopCounter()">Stop</button>
   <button ng-click="startCounter()">Start</button>
   </div>
</div>
Try!

Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus