Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Jeff Coughlin
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Jeff Coughlin@jeffcoughlin )

Services And Factories Are Instantiated On-Demand In AngularJS

By Ben Nadel 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.




Reader 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.

Reply to this Comment

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.

Reply to this Comment

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?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.