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

Route Resolution Using Factory Functions vs. Services In AngularJS

By Ben Nadel on

The other day, I started digging into route resolution in AngularJS. In that post, I demonstrated that you could delay the $routeChangeSuccess event by creating one or more promises during the route transition. In that post, I was using a resolution factory function; but, I could have also used a Service. That said, the two approaches result in two very different behaviors.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

When defining routes in AngularJS, each route configuration can contain a "resolve" object. This resolve object contains a collection of key-value pairs that have to be resolved before the $routeChangeSuccess event will be triggered. Each key defines the name of the resolved value as it will appear in the $route.current.locals object. Each value tells AngularJS how to execute the resolution.

Each value, in the resolve object, can be either a function or a string. If it is a function, this function is expected to return the resolution (or the promise of a resolution) for that key. If it is a string, AngularJS will get the value out of the dependency-injection container and use that value as the resolution (or the promise of a resolution) for that key.

Under the hood, the factory function is invoked using $injector.invoke(). This means that it can make use of dependency injection. The service (string value), on the other hand, is accessed using $injector.get() - the same way that any service is accessed in AngularJS. This difference is important.

When you require a service in AngularJS, the injector looks for the service in the internal cache. If it exists, it returns it from the cache. If it doesn't exist, it instantiates it, sticks it in the cache, and then returns it. The key take-away here is that a service is only ever instantiated once, on-demand, the first time that it is accessed. All other requests for said service are pulled directly from the cache.

In terms of route resolution, this makes a huge difference. If you use a factory function to resolve a route value, the factory function is invoked every time the route is accessed; there is no internal caching involved in this workflow. If, on the other hand, you use a service as the route value, the service is only ever instantiated once. This means that your route value is only ever calculated once - not every time your route is accessed.

To see this in action, take a look at the following demo. In the code, I have two routes - one that uses a factory function to produce a route resolution and one that uses a service as the route resolution:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Route Resolution Using Factory Functions vs. Services In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Route Resolution Using Factory Functions vs. Services In AngularJS
  • </h1>
  •  
  • <p>
  • <a href="#/a">Section A</a>
  • &mdash;
  • <a href="#/b">Section B</a>
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.8.min.js"></script>
  • <script type="text/javascript" src="../../vendor/angularjs/angular-route-1.3.8.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [ "ngRoute" ] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I setup the routes for the application.
  • app.config(
  • function( $routeProvider ) {
  •  
  • // Define routes for the application.
  • $routeProvider
  • .when(
  • "/a",
  • {
  • resolve: {
  • // In this case, we're going to define the route
  • // resolution as a factory function. This function will
  • // be invoked every time the route is accessed and should
  • // return a value or a promise.
  • // --
  • // NOTE: Accessed using $injector.invoke().
  • aResolutionFactory: function() {
  •  
  • console.log( "A-resolver" );
  •  
  • return( "a-value-or-promise" );
  •  
  • }
  • }
  • }
  • )
  • .when(
  • "/b",
  • {
  • resolve: {
  • // In this case, we're going to define the route
  • // resolution as a service. The String here represents
  • // the name of a service within the Dependency-Injection
  • // container. This service will be instantiated the first
  • // time that this route is accessed. For every subsequent
  • // route access, the already-instantiated service is used.
  • // --
  • // NOTE: Accessed using $injector.get().
  • bResolutionService: "bResolutionService"
  • }
  • }
  • )
  •  
  • // And, if nothing else matches, just redirect to section A.
  • .otherwise({
  • redirectTo: "/a"
  • })
  • ;
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I provide a service that loads a route resource and returns the resolved
  • // value (or a promise of the resolved value).
  • app.factory(
  • "bResolutionService",
  • function( $q ) {
  •  
  • console.log( "B-resolver" );
  •  
  • return( $q.when( "b-value-or-promise" ) );
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Since we don't have any controllers in this demo, we have to inject the
  • // $route service at least ONCE in order to get the routing module to fully load.
  • // Without this step, none of the other routing will work.
  • app.run(
  • function loadRoute( $route ) {
  •  
  • // Just forcing $route service to be instantiated.
  •  
  • }
  • )
  •  
  • </script>
  •  
  • </body>
  • </html>

When you use an AngularJS service as a route resolution, it helps to think about this as a special use-case for services. Usually, services provide an API to access data asynchronously; but, when used for the resolution of a route, the service, itself, is the asynchronous data.

I didn't articulate that difference very well. In a practical sense, a typical service provides an API that subsequently returns a promise. But, for route resolution, the service, itself, is the promise.

NOTE: A route resolution service can return static data synchronously. But, if you're going to do that, you might as well just calculate that data, on demand, and inject it into any Controller that needs it - no need to deal with route resolution at all.

If I run the above page and toggle back and forth between route A and route B, I get the following console output:


 
 
 

 
 Route resolution factory functions are called every time the route is accessed; route resolution services, on the other hand, are only called once. 
 
 
 

Notice that only the route A resolution is logged more than once. This is because the resolution factory function is called every time the route is accessed. The resolution service, on the other hand, is only instantiated once; then, the same cached promise is used over and over again to resolve the route whenever it is accessed.




Reader Comments

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.