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

windowdocument 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, we return here to prevent Angular from trying to initialize itself a second time.
  • bindJQuery() checks to see if jQuery is present. Angular abstracts DOM manipulation with angular.element which provides a similar API to jQuery, and rather than creating that API again, this function binds jQuery to angular.element if it’s available.
  • publishExternalAPI() sets up the global angular 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!

Leave a Reply

Your email address will not be published. Required fields are marked *