Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Steven Guitar
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Steven Guitar ( @stguitar )

Services And Factories Are Instantiated On-Demand In AngularJS

By on

This is a really small post, but something that might not be entirely obvious to people coming into AngularJS - services and factories are instantiated on-demand, when they are required (ie, injected) in some other context, like a Controller. For app setup, this is usually a non-issue; but, if you need to hook into the tear-down phase of an app (ex. window.onload, window.onbeforeunload), this on-demand instantiation can lead to unexpected outcomes if you don't understand it.

Run this demo in my JavaScript Demos project on GitHub.

To demonstrate, I have a tiny app that defines two services. One of the services is injected into the top level controller; the other into a sub-controller that is conditionally loaded based on the ngIf directive. Both of these services log their instantiation and then hook into the window "unload" event.

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />

	<title>
		Services And Factories Are Instantiated On-Demand In AngularJS
	</title>

	<style type="text/css">

		a[ ng-click ] {
			cursor: pointer ;
			text-decoration: underline ;
		}

	</style>
</head>
<body ng-controller="AppController">

	<h1>
		Services And Factories Are Instantiated On-Demand In AngularJS
	</h1>

	<p>
		<a ng-click="toggleContainer()">Toggle Container</a>
	</p>

	<div ng-if="isShowingContainer">

		<p ng-controller="SubController">
			Woot! Check out the logs, yo!
		</p>

	</div>


	<!-- 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 run when the application is bootstrapped, before any of the other entities
		// in the module. This is as close to a "main" method as you get in AngularJS.
		app.run(
			function initializeApplication( /* serviceOne, serviceTwo */ ) {

				// ... this does nothing but require the injected services to be
				// instantiated before other parts of the module run.

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I control the root of the application.
		app.controller(
			"AppController",
			function( $scope, serviceOne ) {

				// I determine the visibility of the sub-container (and its controller).
				$scope.isShowingContainer = false;


				// ---
				// PUBLIC METHODS.
				// ---


				// I toggle the visibility of the sub-container.
				$scope.toggleContainer = function() {

					$scope.isShowingContainer = ! $scope.isShowingContainer;

				};

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I control the sub-module of the application.
		app.controller(
			"SubController",
			function( $scope, serviceTwo ) {

				// ... just here to demonstrate service instantiation.

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I am invoked using the "new" keyword, treating the definition as a Constructor
		// of the target service.
		app.service(
			"serviceOne",
			function Constructor( $window ) {

				console.log( "Service one instantiated." );

				// Hook into the unload event of the app clean up service (ex, persist
				// relevant data to localStorage).
				$window.addEventListener(
					"unload",
					function handleOnUnloadEvent( event ) {

						console.warn( "Service ONE hooked into window unload event." );

					}
				);

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I am invoked as a normal method, treating the definition as a provider of the
		// target service.
		app.factory(
			"serviceTwo",
			function Factory( $window ) {

				console.log( "Service two instantiated." );

				// Hook into the unload event of the app clean up service (ex, persist
				// relevant data to localStorage).
				$window.addEventListener(
					"unload",
					function handleOnUnloadEvent( event ) {

						console.warn( "Service TWO hooked into window unload event." );

					}
				);

				return( {} );

			}
		);

	</script>

</body>
</html>

When you run the page, what you'll see is that serviceTwo isn't instantiated until the sub-controller is required. At that point, it is instantiated on-demand and injected into the SubController(). This means that our serviceTwo instance won't perform any clean-up unless the SubController is used during the application lifecycle.

If I open this page and refresh the window without toggling the SubController, I get the following [persisted] console output:

Service one instantiated.
Service ONE hooked into window unload event.
Service one instantiated.

As you can see, serviceTwo was neither instantiated nor did it hook into the window unload event. This time, if I run the page, toggle the sub-container, and then refresh the page, I get following [persisted] console output:

Service one instantiated.
Service two instantiated.
Service ONE hooked into window unload event.
Service TWO hooked into window unload event.
Service one instantiated.

As you can see, this time, with both Controllers invoked, both services were instantiated and were able to hook into the window unload event.

If you truly need a service to be instantiated during the lifetime of an application, regardless of the user behavior, you can always inject it into a .run() block for the module. The .run() blocks get executed after the application is bootstrapped and use dependency injection just like any other controller or service. As such, you can use them to force the instantiation of any service or factory in your application.

Most of the time, the on-demand instantiation nature of an AngularJS application is not something that you ever need to think about. But, if you start to deal with things like localStorage persistance or other "teardown" tasks, the module lifecycle is something that you need to take into account. Otherwise, you may run into unexpected behaviors.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Hello,
I want to display username in user welcome page.how could i do it from login page using services,controller.Any one please help me.

1 Comments

Hello,
I want to display username in user welcome page.how could i do it from login page using services,controller.Any one please help me.

1 Comments

I once had a bad time figuring out why a service injected to a controller wasn't instantiated. I figured that it's not enough to declare it the DI but you actually have to use it inside the controller's function.
Now I need a certain service to be instantiated to handle events, to solve this I'm using injector's invoke method. Is it a good solution?

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel