Dissecting Angular: Initializing An App
This is the 2nd post in a series on the internals of Angular. Today we’ll be covering how Angular initializes itself and sets up applications to be bootstrapped.
Starting Angular
Jumping back into the /src
of Angular, the first two files we need to look at to understand how the initialization process works are angular.prefix
and angular.suffix
. We talked about these last time, but to recap, these files are prepended (prefix) or appended (suffix) during the build process.
In angular.prefix
we see that it all starts off with an immediately invoked function expression, similar to what you’ll see in jQuery and most other popular libraries. Here’s the gist of how it works.
(function(window, document, undefined) { // prefixed
// angular...
})(window, document); // suffixed
window
, document
and undefined
are passed in just to make sure that it has access to the originals in case someone has redefined them before loading Angular. Adding (window, document)
at the end of the function executes it immediately and passes in window
and document
as arguments. JavaScript automatically assigns arguments to undefined
if they are not passed to a function, so the third argument is doing the same thing by leaving it undefined.
The next thing we want to look at is the contents of angular.suffix
.
if (window.angular.bootstrap) {
//AngularJS is already loaded, so we can return here...
console.log('WARNING: Tried to load angular more than once.');
return;
}
//try to bind to jquery now so that one can write angular.element().read()
//but we will rebind on bootstrap again.
bindJQuery();
publishExternalAPI(angular);
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
})(window, document);
The main part we’re interested in today is the call to angularInit()
, but let’s go over what else is here quickly.
if (window.angular.bootstrap) {...
is checking to see if Angular has already been setup. If it has, wereturn
here to prevent Angular from trying to initialize itself a second time.bindJQuery()
checks to see if jQuery is present. Angular abstracts DOM manipulation withangular.element
which provides a similar API to jQuery, and rather than creating that API again, this function bindsjQuery
toangular.element
if it’s available.publishExternalAPI()
sets up the globalangular
element with all the publicly available methods. Everything you see in the documentation for the core module is set up through this method. Another post will cover this in more detail.
The first thing most people probably notice is that this (document).ready(function() {
stuff looks awfully familiar. We are actually using the same technique that people use in their jQuery scripts here. This watches for the DOMContentLoaded
event (for normal browsers) and runs the callback function that gets passed in once it fires.
angularInit()
This method’s sole purpose is to sniff out any Angular application instances declared on the page. I’ve annotated the source of the method below, but the gist of what it’s doing is finding anything that looks like it is declaring a new Angular application. If it finds an app, it sends it off to bootstrap()
.
function angularInit(element, bootstrap) {
// Declare some variables to work with later.
var elements = [element],
appElement,
module,
// Notice, these are all acceptable attribute values for initializing an app
names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
NG_APP_CLASS_REGEXP = /sng[:-]app(:s*([wd_]+);?)?s/;
// A helper method for building up an array of elements to search through.
function append(element) {
element && elements.push(element);
}
// Loop through the names array and query anything that could be an Angular app,
// then append the queried elements to our elements array.
forEach(names, function(name) {
names[name] = true;
append(document.getElementById(name));
name = name.replace(':', ':');
if (element.querySelectorAll) {
forEach(element.querySelectorAll('.' + name), append);
forEach(element.querySelectorAll('.' + name + ':'), append);
forEach(element.querySelectorAll('[' + name + ']'), append);
}
});
// Loop through our elements array and see if there's anything that should be
// bootstrapped as an application and assign it to appElement.
forEach(elements, function(element) {
if (!appElement) {
// See if a class name exists that would be trying to register an app
var className = ' ' + element.className + ' ';
// See if there is a module name passed in with the class
var match = NG_APP_CLASS_REGEXP.exec(className);
// If there is a class on the element we passed in that matches the app class regex...
if (match) {
appElement = element;
module = (match[2] || '').replace(/s+/g, ',');
} else {
// Loop through the attributes of the element and see if any of the
// attribute names match keys from our names array
forEach(element.attributes, function(attr) {
if (!appElement && names[attr.name]) {
appElement = element;
module = attr.value;
}
});
}
}
});
// Bootstrap our element that has the application declaration
if (appElement) {
// There won't always be a module defined, so pass an empty array if none is given
bootstrap(appElement, module ? [module] : []);
}
}
At this point we’ve searched through the DOM and found an element that is supposed to be an Angular app. Now we’re sending it off to be bootstrapped, but we’ll hold off on dissecting that until next time. Don’t worry, we’ll be getting to the good stuff soon!
Source: