Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created August 25, 2014 11:49
Show Gist options
  • Save bennadel/9df09b930516e5f37127 to your computer and use it in GitHub Desktop.
Save bennadel/9df09b930516e5f37127 to your computer and use it in GitHub Desktop.
Creating A Reusable Timer In AngularJS
<script type="text/javascript">
function prepareToDoSomething() {
if ( timer ) {
clearTimeout( timer );
}
timer = setTimeout( doSomething, duration );
}
var timer = null;
var duration = 2000;
</script>
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Creating A Reusable Timer In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Creating A Reusable Timer In AngularJS
</h1>
<p>
<a ng-click="handleClick()">Click me</a> to star the timer!
</p>
<p ng-if="logExecutedAt">
Executed: {{ logExecutedAt.getTime() }}
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.22.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the root of the application.
app.controller(
"AppController",
function( $scope, timer ) {
// I am a timer that will invoke the given callback once the timer has
// finished. The timer can be reset at any time.
// --
// NOTE: Is a thin wrapper around $timeout() which will trigger a $digest
// when the callback is invoked.
var logClickTimer = timer( logClick, timer.TWO_SECONDS );
$scope.logExecutedAt = null;
// When the current scope is destroyed, we want to make sure to stop
// the current timer (if it's still running). And, give it a chance to
// clean up its own internal memory structures.
$scope.$on(
"$destroy",
function() {
logClickTimer.teardown();
}
);
// ---
// PUBLIC METHODS.
// ---
// I handle the click event. Instead of logging the click right away,
// we're going to throttle the click through a timer.
$scope.handleClick = function() {
$scope.logExecutedAt = null;
logClickTimer.restart();
};
// ---
// PRIVATE METHODS.
// ---
// I log the fact that the click happened at this point in time.
function logClick() {
$scope.logExecutedAt = new Date();
}
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I create timers that wrap the $timeout and provide easy ways to cancel and
// reset the timer.
app.factory(
"timer",
function( $timeout ) {
// I provide a simple wrapper around the core $timeout that allows for
// the timer to be easily reset.
function Timer( callback, duration, invokeApply ) {
// Store properties.
this._callback = callback;
this._duration = ( duration || 0 );
this._invokeApply = ( invokeApply !== false );
// I hold the $timeout promise. This will only be non-null when the
// timer is actively counting down to callback invocation.
this._timer = null;
}
// Define the instance methods.
Timer.prototype = {
// Set constructor to help with instanceof operations.
constructor: Timer,
// I determine if the timer is currently counting down.
isActive: function() {
return( !! this._timer );
},
// I stop (if it is running) and then start the timer again.
restart: function() {
this.stop();
this.start();
},
// I start the timer, which will invoke the callback upon timeout.
start: function() {
var self = this;
// NOTE: Instead of passing the callback directly to the timeout,
// we're going to wrap it in an anonymous function so we can set
// the enable flag. We need to do this approach, rather than
// binding to the .then() event since the .then() will initiate a
// digest, which the user may not want.
this._timer = $timeout(
function handleTimeoutResolve() {
try {
self._callback.call( null );
} finally {
self = self._timer = null;
}
},
this._duration,
this._invokeApply
);
},
// I stop the current timer, if it is running, which will prevent the
// callback from being invoked.
stop: function() {
$timeout.cancel( this._timer );
this._timer = false;
},
// I clean up the internal object references to help garbage
// collection (hopefully).
teardown: function() {
this.stop();
this._callback = null;
this._duration = null;
this._invokeApply = null;
this._timer = null;
}
};
// Create a factory that will call the constructor. This will simplify
// the calling context.
function timerFactory( callback, duration, invokeApply ) {
return( new Timer( callback, duration, invokeApply ) );
}
// Store the actual constructor as a factory property so that it is still
// accessible if anyone wants to use it directly.
timerFactory.Timer = Timer;
// Set up some time-based constants to help readability of code.
timerFactory.ONE_SECOND = ( 1 * 1000 );
timerFactory.TWO_SECONDS = ( 2 * 1000 );
timerFactory.THREE_SECONDS = ( 3 * 1000 );
timerFactory.FOUR_SECONDS = ( 4 * 1000 );
timerFactory.FIVE_SECONDS = ( 5 * 1000 );
// Return the factory.
return( timerFactory );
}
);
</script>
</body>
</html>
@karthikreddyr
Copy link

Excellent wrapper... how to write jasmine unit test case..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment