<!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>