Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Rachit Arora
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Rachit Arora@rachitusc )

Creating A Factory Provider For The Configuration Phase In AngularJS

By Ben Nadel on

Yesterday, in my post on consuming Data URIs in an AngularJS application, I created a caching service that would automatically evict cache entires after a static period of time. As I was building that, it occurred to me that the eviction-timeout is probably something that should be exposed during the configuration phase of the application. But, I've never actually created an explicit Provider before. So, for this post, I wanted to wrap my head around how Factory Providers work in AngularJS.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

In AngularJS, a provider is nothing but an object that implements an interface that includes the $get() method. This $get() method is your service factory. In fact, when you use module methods like .factory() and .service(), AngularJS is creating the provider and assigning the $get() method for you, behind the scenes. That makes the following two code blocks functionally equivalent:

  • // Define the provider and the factory method.
  • app.provider(
  • "thingie",
  • {
  • $get: function( $q, $rootScope ) {
  •  
  • return( "Thingie 1 for the win!" );
  •  
  • }
  • }
  • );
  •  
  • // Define only the factory method - the provider will be automatically created by
  • // AngularJS, behind the scenes.
  • app.factory(
  • "thingie",
  • function( $q, $rootScope ) {
  •  
  • return( "Thingie 2 for the win!" );
  •  
  • }
  • );

The value in explicitly defining a provider is that your provider can be exposed during the configuration phase of the application, before all the services are instantiated. This gives your configuration code a chance to modify service behaviors, like adding HTTP interceptors in order to track HTTP activity.

Or, in my case, exposing the cache eviction timeout so that my Data URI caching service won't use the default timeout.

To try this out for myself, I created a simple Greeter service that would generate greetings for a given name. It defines a default greeting template; but, the provider exposes accessors for the template such that it can be changed during the configuration phase:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Creating A Factory Provider For The Configuration Phase In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Creating A Factory Provider For The Configuration Phase In AngularJS
  • </h1>
  •  
  • <p>
  • <em>All of the action takes place in the console.</em>
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.13.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the application is being bootstrapped, it runs the configuration phase
  • // first. During this phase, we have access to all the Providers, but NOT to the
  • // actual service objects that will be created.
  • app.config(
  • function configureApplication( greeterProvider ) {
  •  
  • // Set the greeting that our greeter will use.
  • // --
  • // NOTE: After the configuration phase is over, there will be no public
  • // way to change this unless you cache a reference to the provider.
  • greeterProvider.setGreeting( "Good morning, %s. You are looking marvelous in that pant-suit!" );
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the application is being bootstrapped, it will run the configuration
  • // phase first and then it will execute the "Run" phase. At that point, all of
  • // the run() blocks are executed. During this phase, we no longer have access
  • // to any of the provider; but, we can finally access the services.
  • app.run(
  • function startApplication( greeter ) {
  •  
  • // Consume the greeter that we configured in the previous Config phase.
  • console.info( "Testing greeter in .run() phase." );
  • console.log( greeter.greet( "Sarah" ) );
  • console.log( greeter.greet( "Anna" ) );
  • console.log( greeter.greet( "Kim" ) );
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I provide a service for creating greetings.
  • // --
  • // NOTE: The provider can have other providers injected, but it cannot inject
  • // services as this will be created during the configuration phase, before
  • // services have been made available.
  • // --
  • // NOTE: The ProvideGreeter() function is going to be instantiated using the
  • // "new" operator; as such, we could use the "this" reference to define object
  • // properties and methods. But, I'm used to returning a public API.
  • app.provider(
  • "greeter",
  • function ProvideGreeter() {
  •  
  • // I am the greeting template.
  • var greeting = "Hello, %s.";
  •  
  • // Return the public API for the provider.
  • return({
  • getGreeting: getGreeting,
  • setGreeting: setGreeting,
  •  
  • // The provider must include a $get() method that will be our
  • // factory function for creating the service. This $get() method
  • // will be invoked using $injector.invoke() and can therefore use
  • // dependency-injection.
  • $get: instantiateGreeter
  • });
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I return the current greeting template.
  • function getGreeting() {
  •  
  • return( greeting );
  •  
  • }
  •  
  •  
  • // I set the new greeting template. It must contains at least one
  • // instance of the "%s" or "%S" so that we know how to formulate the
  • // interpolated message.
  • function setGreeting( newGreeting ) {
  •  
  • testGreeting( newGreeting );
  •  
  • greeting = newGreeting;
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I test a new greeting for valid structure.
  • // --
  • // CAUTION: Throws "InvalidGreeting" error if greeting is invalid.
  • function testGreeting( newGreeting ) {
  •  
  • if ( ! newGreeting ) {
  •  
  • throw( new Error( "InvalidGreeting" ) );
  •  
  • }
  •  
  • if ( newGreeting.search( "%[sS]" ) === -1 ) {
  •  
  • throw( new Error( "InvalidGreeting" ) );
  •  
  • }
  •  
  • }
  •  
  •  
  • // ---
  • // FACTORY METHOD.
  • // ---
  •  
  •  
  • // I create the actual greeter service.
  • // --
  • // NOTE: This function is the same function we could have defined if we
  • // had just used .factory() instead of .provider(). As such, this method
  • // is invoked using dependency injection and can inject other services.
  • function instantiateGreeter() {
  •  
  • // Return the public API.
  • return({
  • greet: greet
  • });
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I return a greeting message for the given name.
  • function greet( name ) {
  •  
  • return(
  • greeting.replace(
  • /%s/gi,
  • function interpolateName( $0 ) {
  •  
  • return( ( $0 === "%s" ) ? name : ucase( name ) );
  •  
  • }
  • )
  • );
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I safely convert the given value to upper-case.
  • function ucase( name ) {
  •  
  • return( ( name || "" ).toUpperCase() );
  •  
  • }
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the provider exposes the following methods:

  • getGreeting()
  • setGreeting( template )
  • $get()

These methods - less the $get() method - can be consumed during the configuration phase, but not during run phase of the application. As such, I can change the greeting template during the configuration phase to:

"Good morning, %s. You are looking marvelous in that pant-suit!"

This way, when I run the AngularJS application, we get the following console output:

Testing greeter in .run() phase.
Good morning, Sarah. You are looking marvelous in that pant-suit!
Good morning, Anna. You are looking marvelous in that pant-suit!
Good morning, Kim. You are looking marvelous in that pant-suit!

In this case, I'm defining the Provider as a function. When you do this, AngularJS will treat the function as a Constructor and instantiate it using the "new" operator. This will bind the "this" reference to the provider instance. But, I'm foregoing that opportunity and am using the revealing module pattern, instead, to return the public API for lexically-bound functions.

You can also define the Provider as an object, in which case you just have to make sure that the object has a $get() method which defines your service factory.

In this post, I'm not really adding any new information - there's nothing here that's not plainly in the AngularJS documentation. This post was more of a note-to-self on how to create a Provider and then how to consume that provider in the configuration phase of the application.




Reader Comments

Aren't 'var greeting' and 'greeting' that's accessed within instantiateGreeter two different variables?

Reply to this Comment

This coding will help in my configuration task. Thanks for sharing what you know about it. You just made our lives easier.

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.