The Module Pattern
The Module pattern is a common Javascript coding pattern widely used when developers want discrete JS files/modules.
All of the code that runs inside the function lives in a closure, which provides privacy and state throughout the lifetime of our application. To avoid the use of implicit globals, the Module pattern can specify parameters to the anonymous function.
(
function
(define, angular) {
// ...
}(window.define, window.angular));
|
By passing globals as parameters to our anonymous function, we import them into our code, which is both clearer and faster than implied globals.
Unfortunately, the V8 engine currently manifests Javascript breakpoint bugs with Module patterns and parameters. As such, parameters are NOT currently recommend for development purposes. In addition, since the parameters we would pass are in fact global (define, angular), while cleaner it’s not a necessity to function properly.
Module Exports
Sometimes you don’t just want to use globals, but you want to declare them. We can easily do this by exporting them, using the anonymous function’s return value.
windows.utils = (
function
()
{
// Publish a `log()` utility function
return
{
log :
function
( message ) {
console.log( message );
}
};
}());
|
The example above shows how the anonymous function publishes a *global* `utils` registry with a `log()` function.
Module Exports are not used within our project; as such approaches require explicit assignment to external variables or storage. Instead the AMD pattern is used to manage module exports and dependencies.
Asynchronous Module Definition Pattern
The AMD pattern allows developer to
Define discrete JS modules…modules that may have 1..n external dependencies for other modules, and
Load these modules asynchronously (e.g. at some later time)
Using the RequireJS AMD pattern, let’s see how we could define a Logger class:
/**
* Log provides an abstract method to encapsulate logging messages to the console
* Filename: `myApp/utils/logger.js`
*/
var
dependencies = [ ];
// list of JS file/Module names to be loaded 1st
define( dependencies,
function
()
{
// Publish a `log()` utility function
var
logger = {
log :
function
( message ) { console.log( message ); },
debug :
function
( message ) { console.debug( message ); }
};
return
logger;
});
|
It is important to realize that the define()
does not return a value. Rather the anonymous function specified within define()
returns a value to RequireJS… a value that is registered/cached for later use/injection.
Developers can think of the above code as follows:
Check if the dependencies are loaded (in this case none)
Execute the anonymous function (usually done asynchronously after all the defines() have been loaded and their dependency tree has been constructed.
Store the return value in a definition registry associated with the myApp/utils/logger
key
Inject the stored value into functions that declare logger
as a dependency
Recall that the AMD pattern must return a value… a value that will be stored in the definition registry [for future injection]. The important thing to note is that value can be anything: object, string, array, or even a function.
The specific type of the value returned is entirely dependent on how or what the developer wants to inject into dependent definitions.
RequireJS AMD and Dependency Injection
In addition to the management of import dependencies and registering return values, the AMD pattern has one (1) other very important feature: Dependency Injection.
When the logger
utility is needed within another module, you simply define a dependency to load the logger
module and RequireJS will inject a reference to the logger
value in the AMD definition registry.
A definition is said to be loaded when the anonymous function [with its parameters] is invoked and a value is returned;
/**
* Greeter
* FileName: `myApp/Greeter.js`
*/
define( [
'myApp/utils/logger'
],
function
( logger )
{
// Publish Greeter instance
return
{
welcomeUser :
function
( user ) {
logger.debug(
"Welcome back "
+ user );
}
};
});
|
Notice that the logger
argument in the anonymous function above will now reference the logger
instance that is stored in our definition registry… and this value is generated as the return value from the anonymous function in the myApp/utils/logger
module itself.
Since the dependency chain specifies that the Greeter class cannot be defined until the logger
definition has been loaded, this effectively means that RequireJS has INJECTED the logger value into the Greeter class (during runtime instantiation).
Fully-protected AMD Usages
Finally let’s wrap the RequireJS AMD pattern within the Module pattern:
(
function
())
{
var
numGreetings = 0;
var
dependencies = [
// tell RequireJS to inject the value returned from Logger.js
'myApp/utils/logger'
];
define( dependencies,
function
( logger )
{
// Publish Greeter instance
return
{
welcomeUser :
function
( user )
{
numGreetings += 1;
logger.debug(
"Welcome back "
+ user );
},
reportGreetings :
function
()
{
logger.debug(
"Number of greetings issued = "
+ numGreetings);
}
};
});
}( ));
|
The Module pattern usage (above) has enabled the developer to:
Protect the internals of how the Greeter class is defined
Encapsulate and manage the private global state numGreetings
This approach of defining a RequireJS AMD pattern within a Module pattern is used within every class in my HTML5 project(s).
Developers should note, however, that the RequireJS AMD dependency injection (aka DI) is NOT an AngularJS injection process. RequireJS AMD DI is used only to inject functionality that is external and separate of any AngularJS instance constructions.
In subsequent sections, we will see how Angular DI differs and how both DIs can be used together in synergistic solutions.
AngularJS Dependency Injection
AngularJS provides a unique and powerful support for dependency injection that is distinct from RequireJS AMD dependency injections.
Think of AngularJS DI as injecting instances
and RequireJS DI as injecting classes
or constructors
Similar to RequireJS, AngularJS provides mechanisms for building a registry of instances managed by keywords: factory()
, service()
, and controller()
. All three (3) functions take:
keyword: consider this the variable instance name; which is used to inject as a constructor parameter into other instance constructions.
function: consider this the construction function.
angular
.factory(
"session"
,
function
()
{
return
Session();
})
.service(
"authenticator"
,
function
()
{
return
new
Authentication();
})
.controller(
"loginController"
,
function
( session, authenticator)
{
return
new
LoginController( session, authenticator );
});
|
Using a construction function allows AngularJS to internally defer invocation until the instance is actually needed.
But notice how the construction function for loginController
requires the session
and authenticator
instances as function parameters ? These instances are injected into the function invocation by AngularJS when AngularJS instantiates an instance of the LoginController.
Using AngularJS Annotations for Injection
Let’s reconsider the AngularJS registration of the /myApp/controllers/LoginController.js
module as a controller:
(
function
()
{
define(
function
()
{
var
LoginController =
function
( session, authenticator )
{
// Publish instance with simple login() and logout() APIs
return
{
login :
function
( userName, password )
{
authenticator.login( userName, password );
},
logout :
function
()
{
authenticator.logout().then(
function
()
{
session.clear();
});
}
};
};
// Publish the constructor function
return
LoginController;
});
}());
|
Here the module published the expected construction function… where the LoginController
reference is actually a Function
.
As I mentioned earlier, AngularJS injects parameter values as constructor arguments. But how does AngularJS know which instances/values to inject? JavaScript does not have annotations, and annotations are needed for dependency injection.
Well, you have to tell AngularJS the names of the instances to be injected. The following are all valid ways of annotating functions with injection arguments and are equivalent:
inferred – Use the Function.toString()
method to parse and determine the argument names. Note: this only works if the code is not minified/obfuscated
annotated – Adding an $inject
property to the function where the parameters are specified as elements in an array
inline – as an array of injection names, where the last item of the array is the function to call
Both the annotated and inline techniques also continue to work properly even when we use Javascript minification to obfuscate and compress our production application code. And for developers concerned about performance, the AngularJS engine actually scans for the annotation method in the following priority order: $inject, inline, inferred.
AngularJS inline support is an elegant technique where developers can use an array where each element of the array is a string whose names corresponds to an argument of the function to be invoked AND the last element is the actual constructor function. I personally call this technique a constructor array
.
I find the built-in support for either a constructor function or an array is an incredibly powerful, ingenious solution. Here is how I use this inline, constructor array
technique as part of an AngularJS configuration:
angular
.controller(
"LoginController"
, [
"session"
,
"authenticator"
,
function
( session, authenticator)
{
return
new
LoginController( session, authenticator );
}]);
|
This works… but developers should immediately note two (2) concerns with this solution:
The AngularJS registration of the “LoginController” code is much messier using the inline, construction array.
If the LoginController implementations changes and the constructor arguments are different, developers must remember to also modify/update the construction array above.
As mentioned earlier, Angular supports the $inject annotation technique (that also supports minification issue). Instead of using constructor arrays, developers can attach a $inject
property to the constructor function. The value of this property is an array of string names of the parameters used as constructor arguments:
LoginController.$inject = [
"session"
,
"authenticator"
];
|
Note: This technique has been deprecated; since we want to use inline, Constructor Arrays with RequireJS DI.
Using Constructor Arrays within AngularJS
We can easily refactor our code to both resolve the above two issues and support both annotation and minification… by using inline, constructor Arrays
instead of constructor Functions
. The trick is to have RequireJS inject the inline, constructor array into the module where we configure AngularJS.
The code below will demonstrate. First, let’s update the LoginController module to publish an inline, constructor array instead of a function:
(
function
()
{
define(
function
()
{
var
LoginController =
function
( session, authenticator )
{
// Publish instance with simple login() and logout() APIs
return
{
login :
function
( userName, password ) {
/***/
},
logout :
function
() {
/***/
}
};
};
// Publish the constructor/construction array
return
[
"session"
,
"authenticator"
, LoginController];
});
}());
|
Now our AngularJS startup configuration code is great simplified:
(
function
()
{
var
dependencies = [
'myApp/model/Session'
,
'myApp/services/Authenticator'
,
'myApp/controllers/LoginController'
];
define( dependencies,
function
( Session, Authenticator, LoginController )
{
angular
.factory(
"session"
, Session )
.service(
"authenticator"
, Authenticator )
.controller(
"loginController"
, LoginController );
});
}());
|
Developers should note several interesting aspects to the above code.
Here we have leveraged the power of the RequireJS AMD return values which are injected in as parameters to the define()
anonymous function. These parameters look like class references… but are actually AngularJS constructor arrays. And the result configuration code is SIGNIFICANTLY easier to maintain. In fact, now the configuration code indicates nothing about annotations or minification-safety or argument-name specifications… it just works!
The AMD injected parameters are then used within AngularJS to support dynamic construction and subsequent dependency injection. AngularJS will detect that constructor arrays are specified, determine the appropriate DI values that must be injected as constructor arguments, and then invoke the constructor function. And finally the returned value from the constructor function is stored within an Angular registry; by its variable name.
I must emphasize that when the constructor function is invoked, the published or `return` value MUST still be an appropriate value or object instance. This value/instance will be cached by AngularJS for subsequent future injections.
Note that the registration keywords [used in the sample code above] session
, authenticator
, and loginController
are lowercase because their respective values are instances created from the invocation of their constructor functions. In contrast, upper camel case names indicate types that are actually classes, constructors, or functions… type references that have been injected by RequireJS.
AMD Injection Considerations
As stated above, in most cases the RequireJS AMD dependency allows us to inject classes or `constructor arrays`. Sometimes, it is expedient to also have RequireJS inject a function that is NOT intended for internal AngularJS consumption.
As one example, let’s consider a `supplant` function that provides features to build complex strings with tokenized parameters. In our example below, we want to build strings and log messages independent of the AngularJS $log mechanisms and processes. So let’s inject the supplant
function [published in the myApp/utils/supplant.js Module]… along with our other classes: Session
, Authenticator
, LoginController
:
(
function
()
{
var
dependencies = [
'myApp/utils/supplant'
,
'myApp/model/Session'
,
'myApp/services/Authenticator'
,
'myApp/controllers/LoginController'
];
define( dependencies,
function
( supplant, Session, Authenticator, LoginController )
{
console.log( supplant (
"Added dependency `supplant()` from the {0} module."
,
dependencies
));
angular
.factory(
"session"
, Session )
.service(
"authenticator"
, Authenticator )
.controller(
"loginController"
, LoginController );
});
}());
|
Here the supplant
function is used only with the console.log()
and is not used with AngularJS at all.
This simple example shows how easy it is to use RequireJS to inject both (a) functions for general use and (b) Classes (constructor functions or constructor arrays) for AngularJS instantiation and DI usages.