Skip to main content
Ben Nadel at BFusion / BFLEX 2010 (Bloomington, Indiana) with: Daria Norris and Steve Withington
Ben Nadel at BFusion / BFLEX 2010 (Bloomington, Indiana) with: Daria Norris Steve Withington

Defining Instantiatable Classes In The AngularJS Dependency Injection Framework

By
Published in Comments (18)

One of the key features of AngularJS is its dependency injection (DI) framework. AngularJS provides several convenience methods that allow you to populate the DI injector with values: service(), factory(), and value(). When you first get into AngularJS, the difference between these three methods can be fairly confusing. But, in the end, they are all basically the same; they vary only in the timing and opportunity of dependency injection and instantiation. Most of the time, you're probably providing classes that AngularJS will implicitly instantiate and cache. But, what if you want to define a class - a "bean" - that you wish to explicitly instantiate at a later time.

AngularJS will implicitly instantiate the function that you pass to the service() method. As such, your best bet for defining explicitly-instantiatable classes is to use either the factory() method or the value() method. Personally, I prefer to use the factory() method since its allows for dependency-injection outside of the constructor that we are defining.

NOTE: I believe the only true difference between the service() method and the factory() method is the use of the "new" construct; service() uses it, factory() does not. Other than that, they do the same exact thing.

The factory() method gives you a sandbox in which to define your objects; then, it uses your return value to populate the dependency injection framework. In our case, the "injectable" is the class constructor; so, it is the constructor that we must return from the factory.

In the following demo, I'm going to define a "Friend" class that we can inject and then instantiate in other parts of our AngularJS application.

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

	<title>
		Defining Instantiatable Classes In AngularJS
	</title>
</head>
<body>

	<!-- Load jQuery and AngularJS from the CDN. -->
	<script
		type="text/javascript"
		src="//code.jquery.com/jquery-2.0.3.min.js">
	</script>
	<script
		type="text/javascript"
		src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js">
	</script>

	<!-- Load the app module and its classes. -->
	<script type="text/javascript">


		// Define our AngularJS application module.
		var demo = angular.module( "Demo", [] );


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


		// I run when the AngularJS application has been bootstrapped
		// and the dependency injector is ready to rock and roll.
		demo.run(
			function( Friend ) {


				// Create Tricia using the vanilla constructor.
				var tricia = new Friend( "Tricia", "Smith" );

				// Create Joanna using the convenience class method.
				var joanna = Friend.fromFullName( " Joanna Smith-Joe " );

				// Log the various parts to make sure values were parsed
				// and stored correctly.

				console.log(
					tricia.getFullName(),
					"... or simply,",
					tricia.getFirstName()
				);

				console.log(
					joanna.getFullName(),
					"... or simply,",
					joanna.getFirstName()
				);


			}
		);


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


		// Define an injectable trim method so we can demonstrate the
		// use of dependency injection in the next Factory.
		demo.value( "trim", jQuery.trim );


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


		// To define an instantiatable class / constructor, we can
		// use either the Factory() of the Value() method. I prefer
		// the Factory since it allows for dependency injection.
		demo.factory(
			"Friend",
			function( trim ) {

				// Define the constructor function.
				function Friend( firstName, lastName ) {

					this.firstName = trim( firstName || "" );
					this.lastName = trim( lastName || "" );

				}


				// Define the "instance" methods using the prototype
				// and standard prototypal inheritance.
				Friend.prototype = {

					getFirstName: function() {

						return( this.firstName );

					},

					getFullName: function() {

						return( this.firstName + " " + this.lastName );

					}

				};


				// Define the "class" / "static" methods. These are
				// utility methods on the class itself; they do not
				// have access to the "this" reference.
				Friend.fromFullName = function( fullName ) {

					var parts = trim( fullName || "" ).split( /\s+/gi );

					return(
						new Friend(
							parts[ 0 ],
							parts.splice( 0, 1 ) && parts.join( " " )
						)
					);

				};


				// Return constructor - this is what defines the actual
				// injectable in the DI framework.
				return( Friend );

			}
		);


	</script>

</body>
</html>

Notice that our factory() method returns the "Friend" constructor; it is this constructor that then gets injected into the run() method when the application is ready to execute. In this demo, I am defining my Friend class using prototypal inheritance; however, this would have worked exactly the same if I had defined my class using the revealing module pattern.

When I run the above code, I get the following console output:

Tricia Smith ... or simply, Tricia
Joanna Smith-Joe ... or simply, Joanna

As you can see, I was able to inject and instantiate my Friend class. Furthermore, I was able to precipitate instantiation using both the "new" construct as well as the "static" method - fromFullName() - defined on the class itself. The dependency injection framework in AngularJS is powerful, but at times, confusing. Hopefully this has shed some light on how you can use it to define instantiatable classes.

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

Reader Comments

71 Comments

I've been trying to get myself more familiar with AngularJS, but I have to be honest and say that asking me to put unsupported attributes on elements (like 'ng-app' on the <html> element) makes me shy away from learning this further.

Why could they have not just utilized the data- attributes, which are HTML5 valid and allow the framework to tie into the content without invalidating the code altogether?

6 Comments

Can I just say that I love your blog posts?

Its the structure I like the most.
Words + Code + Video explaining the words === Holy Grail of blog posts

Anyway, keep it up :)

15,902 Comments

@John,

Awesome! Glad to shed some light :)

@Mike,

Thanks!! That really means a lot! I put a lot of work into this stuff, and I really like the way the video gives the "Story" for the blog post. I'm pumped up that it is appreciated!!

15,902 Comments

@Aaron, @Tim,

Tim, thanks for jumping in there! @Aaron, it is a really awesome framework. I would suggest looking into it even if the HTML feels a bit funky.

71 Comments

@Tim

Great to hear! Thanks! I'm a bit of a stickler when it comes to tech that, in its use, would force you to take a step backwards. Glad to see AngularJS is not such a thing!

@Ben

Well if it gets the Nadel seal of approval, that's good enough for me. I'll keep at it!

5 Comments

@Aaron,

http://docs.angularjs.org/guide/directive

"Directives have camel cased names such as ngBind. The directive can be invoked by translating the camel case name into snake case with these special characters :, -, or _. Optionally the directive can be prefixed with x-, or data- to make it HTML validator compliant. Here is a list of some of the possible directive names: ng:bind, ng-bind, ng_bind, x-ng-bind and data-ng-bind."

3 Comments

@Ben -- thanks for the example, just what I was looking for. I've got a follow up question for you here regarding best practices for defining instantiatable more portable classes that may be used inside angular, but also in some other application context -- like a web worker or other non-angular scope.

In that case, how about defining that class outside of angular (either globally, or in some other (non-angular) app namespace ):

myApp.Friend = function () {...}

and then in angular just a factory returning the constructor like :

demo.factory( "Friend",  function( ) {
	return (myApp.Friend);
});

Is that the right way to do it or am i thinking about this wrong?

15,902 Comments

@Greg,

This is exactly what I do as well. And, it has a lot of benefits. Other than making the references easily injectable as dependencies in other AngularJS modules (which has testing benefits and keeps everything nice and decoupled), it _also_ gives you an opportunity to "massage" the data for use in your AngularJS app.

For example, I use lodash/underscore in my AngularJS applications. However, as you're saying, I wrap in a factory call, much like this (I haven't checked this code, FYI):

app.factory(
	"_",
	function() {
 
		// Extend and revoke the global reference.
		var util = Object.create( _.noConflict() );
 
		util.foo = function(){ ... };
		util.bar = function(){ ... };
 
		// Return the "util" reference which now
		// extends the "_" library and will be
		// injectable using the "_" name.
		return( util );
 
	}
);

Here, I'm basically taking the "_" reference _out_ of the global scope and converting it into an AngularJS injectable. Then, I'm also extending it so that my Factory has an opportunity to actually add app-specific utility functions that I use.

Whenever possible, I try to do this so as to decouple my Controllers / Services / Directives from the global scope as much as possible.

Make sense?

1 Comments

I have two questions.
1) Why not `use angular.Module`'s `value` service to register the `Friend` type?
2) Why do you wrap the return value in parens? Eg. `return( Friend );`

2 Comments

@Martin,

1. He explains around 1:25 that he prefer factory over value because it allows for dependency injection.

2. I would assume it's a personal preference thing. If you write your return statement in one line the parentheses makes no difference.

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