In my opinion, one of the biggest hindrances in building a large Angular app is that all pieces must be available and registered at initiation (with some small exceptions). This is largely due to the method of dependency injection (DI) the framework uses - we can load scripts at any point, but registering them with the framework is the hairy bit.

Generally, you cannot bootstrap Angular apps inside each other. Because bootstrapping a new app is the only supported way of easily adding components to your app, that’s a major problem.

However, Angular does not compile the Shadow DOM (I don’t know of a better place this is documented). That means we can create a shadow element, and compile it manually, without worrying about the parent app reaching into it.

Here’s a Working Demo which bootstraps one app in another, using this barrier.

Yes, this is hacky… but 2.0 is a ways out, and given the number of other alternatives for lazily load components of an app, it may be useful.

This is an exploration toward work in our project to support inserting views (angular-driven or not) via a wrapper. In our case, this will likely move to supporting dynamic web components, particularly saved in a database rather than static files.

Interesting Notes

The above demo is simple, but merely attempts to serve as a proof of principle that you can in fact bootstrap one app in another. There’s a few other pieces too it, check out the source.

Batarang (DevTools)

Doesn’t seem to work well here. If you inspect the shadow elements, Batarang (in my version of Chrome at least) does not realize it has moved into a new app, and scope inspection is still of the parent app

Lazy Modules

Very easy to accomplish - check what happens when you click the button in the demo. We can download a script, and then bootstrap our app once the module is ready. You could easily set up more complicated imports using something like RequireJS

You could similarly use HTML imports for bringing in templates lazily as well.

Using templates

Using templates isn’t necessary. You could create any document fragment, so long as it is the child of a (new) shadow root. Templates are inert until they are actually appended to the DOM, so allow you to include the markup without it being touched by the parent app, it being part of layout, etc., e.g. if you need wider support.

Accessing Parent app

Note that creating a module which is listed as a dependency of each app will instantiate itself separately in each app, as is demonstrated.

If you play with the source, and list the parent app as a dependency of the shadow app, you can access it. Specifically, add myApp to the dependencies for the child app, and you can access the $rootScope variable it defines.

This means you can interact between apps fairly easily, but for something more complex, you’d likely want to set up a per-page singleton.

Timing in Bootstrapping

Admittedly, I’m not sure why you need to bootstrap the cloned Node before appending it to the shadow element. If you the same flow from the terminal, there aren’t any problems. Setting a timeout didn’t fix it either… If you have ideas, I’d love to know!

Inert Scripts

Scripts which are in templates are inert until they are actually brought into the DOM - i.e., when the node is appended as a child.

Because we are bootstrapping before appending (until I figure out what is happening there), that means that our scripts in the template are run after the app is bootstrapped. Check the console in the demo.

Code

Check out the whole source at the demo, but here is the directive, which does most of the magic.

Note that this implementation expects an attribute app, which works like ng-app, and an attribute template, which is the template ID to use for creating the shadow content:

<app-wrapper app="shadowApp" template="shadowAppTmpl"></app-wrapper>

And the directive:

.directive('appWrapper', function () {
    //create shadow element, bootstrap app internally

    return function (scope, element, attrs) {
      //create shadow element
      var root = angular.element(element[0].createShadowRoot());

      //get template by ID
      var template = document.getElementById(element.attr('template'));

      //importNode is creating fragment
      var clone = document.importNode(template.content, true);

      // can't clone ShadowRoot node - this errors
      // angular.bootstrap(root, ['shadowApp']);

      //need to bootstrap before we append
      angular.bootstrap(clone,
          [element.attr('app')],
          {debugInfoEnabled: true}
      );

      // appending after bootstrap means inert scripts are run after bootstrap...
      // for better or for worse
      root.append(clone);
    }
  });

Resources

Shadow DOM

HTML5 Rocks Series Part 1, Part 2, Part 3

Working Draft

Lazy Angular Components

I believe it started with Ifeanyi Isitor, using $script.js. Ben Nadel offered his take

The most comprehensive solution I’ve seen is ocLazyLoad, which has support for lazily-registered modules.