47

i've been working on a project that is more like a framework, and has several apps / modules you can install. See it like a basic appstore or google.play store. It's sort of an intranet application, and all modules can be added to your useraccount.

the framework is already in development, but i'm wrapping my head around the applications/modules idea now. (link to a proof of concept in development, can be found here)

an application should be somewhat standalone, and not able to suddenly include scripts from the framework, This is perfectly possible by structuring them in separate modules like so:

angular.module('myApp', []);

however, an app can have templates, scripts, css and it can run on a separate server, so I'm kind of looking for the best way to fetch the script(s) and cssfile(s) and dynamically load them into the app when the user starts the app in from within the framework.

  • currently I'm structuring apps as if they have a main template for example www.framework.com/apps/myapp/views/app.html, for the sake of simplicity i bundle scripts into 1 script file per application, so there is also a www.framework.com/apps/myapp/script.js to be included.

The framework contains a template that loads the apps, and an appController. The template contains this piece:

<div data-ng-controller="AppController" data-ng-include="app.appTemplate">
    <div>loading...</div>
</div>

this basically binds to the $scope.app.appTemplate which is updated when all scripts are loaded, so first it shows a loading template, later after scripts are included in the page it updates the app.appTemplate to the above mentioned main template of an application.

while loading the first index template works, this template is currently loaded with the AppController from the framework, so it is using the $scope of the framework and not it's own script.

I still have to somehow start the app's own angular module, and let it on it's own without running anything in the framework to 'make it work'

I'm still figuring out how to best load the dependent javascript files (will probably use requrejs or other dependency loader) but I have currently no clue how to 'boot' the app without working from within the framework's AppController

EDIT

I created a small demo project to show the problems at hand, full code is visible at git-hub at the moment this project does a few things hard coded, the idea would be that I make those less hard coded when I get the proof of concept right, now it's all about loading the applications within the framework. if that is possible, I can think of where to get the URL's and application names from ...

Sander
  • 13,301
  • 15
  • 72
  • 97

5 Answers5

37

You can't bootstrap a module inside another bootstrapped module. Bootstrapping compiles the view and binds a rootScope to it, traversing it's way through the DOM and setting up scope bindings and executing directive linking functions all the way through. If you do that twice, you're going to run into problems.

You're probably going to have to rethink your architecture. I think perhaps the word "module" or "app" as it pertains to Angular is a misnomer and is leading you down the wrong path.

Each "user installed app" in your application should probably really be controlled by a controller in your app module, or registered to a module referenced by your app module. So you wouldn't be "starting up multiple apps", you'd really just be starting one, referencing the other modules, then using Controllers from those modules to control parts of your view on the screen.

What you'd do is when a new "widget" was installed, you're register it's module file (.js) with the system, which would contain a controller named WidgetCtrl, then when your page loaded, you'd reference the widget's module on your app module. From there it should be available for dynamic assignment to elements using ng-controller and/or ng-include.

I hope that makes sense.

Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • but don't i end up with 100 appmodules, in my dependency array then? and those 100 dependencies, don't they have to be loaded at pageload then? I was trying a dynamic injection, just because i could then insert the scripts when they are needed, not at page load. – Sander Aug 19 '13 at 15:55
  • 7
    this is how we solved it: we have 1 `framework` module, and among it's dependencies is a `framework.applications` module. now, when we dynamically load the scripts for an application, it binds controllers to that framework.applications module. (via the `$controllerProvider.register()` function. so basically we restructured our architecture like you said, to have controllers be the app's logic, and not have a module per app. – Sander Aug 21 '13 at 07:26
  • 1
    What about routing? Can the modules add new routes to the config when they're added? – BenCr Jan 16 '14 at 15:44
  • @Sander, please comment on @BenCr's question. If you dynamically mount a new "app", say `app4` at a certain mount point (`domain.com/app4`), how do you configure all its routes? – Steve Lorimer Apr 12 '14 at 03:32
  • 3
    Yes, routes are stored in a config, and registered on the routeprovider when the app loads. however this is not a failsave system, we learned that the routes tend to create conflicts, /:appname/:otherparameter was a one that gave us headaches quite some time. so when registering the app's routes we did away with the :appname as parameter, and registered it as /app4/:otherparameter. – Sander Apr 13 '14 at 13:44
  • 1
    Did you dynamically registered the routes after bootstrap? What if the user enters the application with a bookmarked URL, does it route correct or does it hit the "otherwise" route? We are trying to build a similiar system and struggle with a lot of things regarding how to isolate applications from each other. It would be great to read a blog post about how you architectured your system. – Abris Jan 18 '15 at 15:26
26

Contrary to currently accepted answer, It is actually possible.

I was working on a similar problem and suggested answer was not acceptable in my case. I had previously written pages with multiple applications but it was years ago and apps were independent of each other. There are two things to do basically:

  • Tell main application to ignore a child element.
  • Bootstrap the child element.

There is an ng-non-bindable attribute which simply tells AngularJS to ignore the element. This handles our first problem.

However when you try to bootstrap the child element; AngularJS will throw an error, telling you that it is already bootstrapped (at least to me, version 1.2.13). Following trick does the job:

<div ng-non-bindable data-$injector="">
  <div id="bootstrap-me">
    <script src="/path/to/app.js"></script>
    <div ng-include="'/path/to/app.html'"/>
  </div>
</div>

This solution is not perfect. Ideally, ng-non-bindable attribute can add required data-$injector attribute to element. I am going to make a feature and hopefully a pull request to AngularJS.


I did not have the chance to make a pull request. Apparently and expectedly I should say, some internals have changed but ng-non-bindable is still working at version 1.3.13 using Ventzy Kunev's demo code (thanks again, see link below).

orcun
  • 14,969
  • 1
  • 22
  • 25
  • 1
    That was really helpful and exactly what I needed. I did not even had to add `data-$injector` (Angular 1.3). Without that solution, I never got directives in nested apps to work - thanks a lot! – Jan Petzold May 28 '14 at 12:05
  • 5
    This used to work just fine for me, in angular 1.2.16. I have just tried 1.2.26 and 1.3.0-rc4 and both these versions now throw the exception: `Error: [ng:btstrpd] App Already Bootstrapped with this Element '<div id="viewAppDiv">'`... anyone else had this problem? – Simon Green Oct 02 '14 at 12:46
  • 2
    This must be the accepted answer. Here is plunkr that demonstrates embed app in modal http://plnkr.co/edit/AVD1BVGb5T5GRH3QwBvl?p=preview Two apps - "modal-app" and "list-app". modal-app shows the modal but its content is managed by controller in embeded list-app. As the modal's html is within template which is not rendered until modal is opened, bootstrapping of list-app is done after that - see the end of modal.js. In real app, I am using "notifier" service, that fires event after modal is rendered, and then my "manager" class bootstraps needed app and modules does't know of each other. – Ventzy Kunev Oct 29 '14 at 23:49
  • 1
    Hi orca, did you put in a pull request to AngularJS, and if so, how have things changed since it was pulled - particularly in regard to the comment by @simon-green - should it now be declared differently for more recent versions of Angular? – Matty J Jan 14 '15 at 04:06
  • Like @Simon-Green, this worked for me in Angular 1.2.18, but is broken in Angular 1.3.7 and 1.3.8. It looks like `ng-non-bindable` still works, but `data-$injector=""` is broken. The element injector gets reset somewhere in Angular internals, even if you try to programmatically nullify it. – sonicwizard Jan 14 '15 at 22:06
  • It's not working for me either (Angular 1.3.2). So it seems this solution is not future-proof. – andyhasit Jan 27 '15 at 14:32
  • @sonicwizard is right, although Ventzy Kunev's plunk is still working at version 1.3.13. I'm guessing this still covers a lot of scenarios as I would use such feature to load other apps dynamically. Hence it may not be worth trying to find what has changed internally. As long as this solution works, I am not voting for such a pull request. – orcun Feb 23 '15 at 14:00
  • 1
    Working fine for me as of Angular 1.4.7, I really love this solution, there is a catch though, you need to bootstrap the inner apps first. Such a shame. – Felype Nov 20 '15 at 14:33
  • 1
    This does no longer seem to work in Angular 1.6. I found another solution which I documented here: http://codepen.io/mthur/pen/QvaoQY – Martin Thurau May 09 '17 at 14:09
  • Thanks @MartinThurau , it still works in 1.7.7 , I can now instanciate / deinstanciate an infinity of sub applications inside of an angularjs 1.7 app :D Should be the good answer ! – Pierre Emmanuel Lallemant May 07 '19 at 22:42
4

well if each sub-app is in its own module, you can just use angular.bootstrap to load that module dynamically. when the url for a specific app loads, you can fetch the necessary script(s), then when the promise resolves, you can do something along the lines of:

// grab a reference to the element where you'll be loading the sub-app
var subapp = document.getElementById('subapp-id');

// assuming the script you get back contains an angular module declaration named
// 'subapp', manually start the sub-app
angular.bootstrap(angular.element(subapp), ['subapp']);

hope this helps

Don Branson
  • 13,631
  • 10
  • 59
  • 101
UnicodeSnowman
  • 1,639
  • 16
  • 13
  • i tried that path, you can see it in the github repo, in the `framework.appController`, the one that checks the `$routeParam` and loads the scripts with promises, that one after scripts are loaded executes: `angular.bootstrap($('#application-container'), [$scope.app.name]);` the `#application-container` is present in the html loaded IN the app, and the app.name is the same as the module name. but this clearly doesn't do the trick. the app's template can't find the controller to load. – Sander Aug 19 '13 at 13:47
  • I hope it's not a problem that you want to bootstrap a module, within an ng-view of another module (the framwwork's ng-app runs on the body tag while the application is just a div within the content area) – Sander Aug 19 '13 at 14:05
  • ah okay, I was looking for something like that in your code to see if you had gone down that route already. I guess I missed it, apologies. Interesting problem, I'll keep working on this. – UnicodeSnowman Aug 19 '13 at 14:09
3

Similar to UnicodeSnowman's answer above, another potential solution that appears to be working for my needs (I built a live Angular editor on a documentation site) is to manually handle the bootstrap process by having a <div id="demos"> that is separate from the main <div id="myApp">.

This article was very helpful to get it working correctly.

General Process

  • Create your main app (I chose to manually bootstrap, but you may be able to use ng-app for this part)
  • Create a new HTML structure/app (in my case the demo app):
  • Append it to the demos div with a custom id: someCoolDemoContainer
  • Boostrap the newly created app
  • Move it back into the original app (for layout/positioning purposes)

Code Example (not tested; just shows basic process)

<div id="myApp">
    <h1>Demo</h1>

    <p>Click the button below to checkout the cool demo!</p>

    <button ng-click="showDemo()">Show Demo</button>

    <div class='insertion-point'></div>
</div>

<div id="demos">
</div>

<script type="text/javascript">
    /*
     * Init/bootstrap our main app
     */
    var appContainer = document.getElementById('myApp');

    angular.module('myApp', ['myDependency']);
    angular.bootstrap(appContainer, ['myApp']);

    // Do lots of other things like adding controllers/models/etc.

    /*
     * Init/bootstrap our demo app when the user clicks a button
     */
    function showDemo() {
      // Append our demo code
      $('#demos').append('<div id="someCoolDemoContainer">Angular app code goes here</div>');

      // Bootstrap the new app
      var demoContainer = document.getElementById('someCoolDemoContainer');
      angular.module('someCoolDemo', ['myDependency']);
      angular.module('someCoolDemo').controller('myController', function() { ... });

      angular.bootstrap(demoContainer, ['someCoolDemo']);

      // Re-insert it back into the DOM where you want it
      $('#myApp').find('.insertion-point').append($('#someCoolDemoContainer'));
    }
</script>
Topher Fangio
  • 20,372
  • 15
  • 61
  • 94
  • Quick note about this that I just realized: your main application will have all of it's config functions re-run every time you bootstrap (in this case every time we show a demo). So, as I showed above, you need to make sure that there is a common module that both applications can use as a dependency rather than the demo module depending directly on the application. – Topher Fangio May 08 '15 at 00:38
3

I know this is quite old now but I was looking for a way to embed an AngularJS app within an Angular app and used the answers from this post to do just that so I thought I'd post up the plunker here for anyone else looking for a similar solution.

There were two ways that I found to do it, both used manual bootstrapping of the angularjs app within the ngOnInit of an Angular component:

ngOnInit(): void {
  // manually bootstrap the angularjs app
  angular.bootstrap(document.getElementById('ngListApp'), ['list-app']);
}

Either set the ngNonBindable attribute on the element that will be bootstrapped:

<div ngNonBindable #insert>
  <!-- can insert the angular js template directly here, inside non-bindable element -->
  <div id="ngListApp" ng-controller="ListController as list">
    <input ng-model="inputValue" />
    <ul>
      <li ng-repeat="item in items">{{ item }}</li>
    </ul>
  </div>
</div>

Or inject the angularjs template html into the element in the ngOnInit event handler within an Angular component so that Angular doesn't try to interpret the AngularJS code (especially interpolation of AngularJS properties in the DOM with curly brackets) during compilation.

ngOnInit(): void{
  // insert angularjs template html here
  this.div.nativeElement.innerHTML = this.htmlTemplate;

  // then manually bootstrap the angularjs app
  angular.bootstrap(document.getElementById('ngListApp'), ['list-app']);
}

The Plunker is here:

http://plnkr.co/plunks/0qOJ6T8roKQyaSKI

robpierce5
  • 31
  • 2