Dissecting Angular: Bootstrapping

Continuing on our journey through Angular, today we’re going to cover its bootstrapping process. This is where we’ll take the static contents of a DOM element and turn it into a dynamic Angular app.

angular.bootstrap()

Last time we left off at angularInit() calling bootstrap() and passing in a DOM element and module name. That element will now be compiled inside bootstrap().

Let’s walk through the code. I’ve added comments to explain what’s happening in context.

function bootstrap(element, modules) {
  var doBootstrap = function() {
    // Convert the element we passed in to a jqLite element.
    element = jqLite(element);

    // Check to see if there is an injector setup for the element already. If there is an
    // injector it means this element has already been bootstrapped, so throw an error.
    if (element.injector()) {
      var tag = (element[0] === document) ? 'document' : startingTag(element);
      throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
    }

    // Assign modules to an empty array if it is undefined.
    modules = modules || [];

    // Add a function to the modules array which provides $rootElement to the
    // element that is being bootstrapped.
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);

    // Add ng to the modules array.
    modules.unshift('ng');

    // Create an injector instance and assign it to "injector". More on injectors later.
    var injector = createInjector(modules);

    // Invoke scope.$apply() and inject scope, element, compile, injector and animate
    // as dependencies.
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
       function(scope, element, compile, injector, animate) {
        // Behind the scenes this is just executing the anonymous function and then
        // running a $root.$digest() after it's finished.
        scope.$apply(function() {
          // Setup the injector on the element we are bootstrapping.
          element.data('$injector', injector);
          // Compile our element and link it to the current scope.
          compile(element)(scope);
        });
      }]
    );

    // Return our injector instance.
    return injector;
  };

  // ...
}

There’s a lot to digest here and a number of things that warrant investigation of their own. For this article we are going to focus on the contents of bootstrap(), but we’ll cover things like dependency injection and compilation in future posts. For now just focus on the flow of the app and that at this point we have an instance of the $injectorreturned from our doBootstrap() helper method. Now let’s dissect the rest of bootstrap().

function bootstrap(element, modules) {
  var doBootstrap = function() {
    // ...
  };

  // Setup a regular expression to look for "NG_DEFER_BOOTSTRAP".
  var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;

  // Check window.name against our regex and call doBootstrap() if it fails.
  if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
    return doBootstrap();
  }

  // If we made it this far, remove "NG_DEFER_BOOTSTRAP" from window.name.
  window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');

  // Create a method that allows for extra modules to be added to during the
  // bootstrapping process.
  angular.resumeBootstrap = function(extraModules) {
    // Loop through extraModules and append them to the modules array.
    forEach(extraModules, function(module) {
      modules.push(module);
    });
    doBootstrap();
  };
}

The rest of bootstrap() either invokes doBootstrap() right away or defers bootstrapping until resumeBootstrap() is called. This allows third party tools to tie into the bootstrapping process by appending NG_DEFER_BOOTSTRAP to window.name and then calling angular.resumeBootstrap() with an array of additional modules. This is commonly used for mocking out heavy dependencies while testing.

Ready for Action

Now that our element is compiled with its dependencies and scope, we are ready for action! The element we passed in to angular.bootstrap() is no longer just a static element, it’s a dynamic Angular app that is ready for user interaction.

Leave a Reply

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