7

I have a use case that requires the loading of separate angular applications.

Based on several stack overflow questions and this google thread, it's doable. However, I can't get it to work.

Looking at the documentation:

http://docs.angularjs.org/api/angular.bootstrap

It looks like you need to provide the element (what is the right way to get a handle on the element?), and then how to tie it back to config, controllers, etc. And how would this work with routes? IE how does collision work, ie app a and app b map /foo to /fooa.html and /foob.html respectively... or each app describes its own .otherwise?

Thanks!

Robert Christian
  • 18,218
  • 20
  • 74
  • 89
  • Are these really two distinct applications or are they different routing paths/different views within one application. I ask because this is much talked about subject and has lead to the creation of a great alternative router https://github.com/angular-ui/ui-router which handles complex route and control flows. As well as parallel view sets through those route paths. – Paul Ryan Jun 01 '13 at 03:58
  • I'd be happy to walk you through how to use this for the case you present in your answer below. Unless your requirements are completely about loading of the individual applications. – Paul Ryan Jun 01 '13 at 04:00
  • This is literally multiple apps. Imagine a SOA environment where services are deployed to varying hosts, each with a relatively simple angular UI app (for configuration of that particular service). And a centralizing application (think admin portal/system console) that knows about each one, and can dig into (run) its application without a redirect. Done this way, a service can be directly managed, and also managed via a master interface. – Robert Christian Jun 01 '13 at 04:43
  • Can these services live on different subviews? That is can they be viewed without the others but still have all the requirements listed? I'm envisioning an admin application that allows you to drill to the sub services subviews while maintaining a common admin parent. I'm just not seeing a way to do this with a portal perspective until https://github.com/angular-ui/ui-router/issues/84 is fixed. I've started a portion of this fix at https://github.com/angular-ui/ui-router/pull/115. – Paul Ryan Jun 01 '13 at 06:15

4 Answers4

8

So given the requirement that this be a service driven content the only way I can see to do this is kind of a mix between angular and standard html practices. Effectively you'll want to take a page from the plunker book and use Iframes to contain each individual portlet.

<!doctype html> <html lang="en">

<body ng-app="plunker" ng-controller="MainCtrl">

<!-- define foo -->

<div>
    <ul class="menu">
        <li><a href="#" ng-click="fooRoute='#/foo1'">foo1</a></li>
        <li><a href="#" ng-click="fooRoute='#/foo2'">foo2</a></li>
    </ul>
    <iframe seamless="true" ng-src="foo.index.html{{fooRoute}}"></iframe> </div>

<div>
    <ul class="menu">
        <li><a href="#" ng-click="barRoute='#/bar1'">bar1</a></li>
        <li><a href="#" ng-click="barRoute='#/bar2'">bar2</a></li>
    </ul>
    <iframe seamless="true" ng-src="bar.index.html{{barRoute}}"></iframe> </div>


<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script> <script src="app.js"></script> </body> </html>

Then on each of these portlets you'll want to have a completely separate application (including the loading of resources).

<!doctype html>
<html lang="en">

<body ng-app="fooApp">

<div ng-view></div>


<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script>
  var app = angular.module('fooApp', ['fooApp.controllers']);

    // Configure the app
    app.config(['$routeProvider', function ($routeProvider) {
        $routeProvider.when('/foo1', {template: '<h1>Foo</h1><h2>foo1</h2>', controller: 'MyCtrl1'});
        $routeProvider.when('/foo2', {template: '<h1>Foo</h1><h2>foo2</h2>', controller: 'MyCtrl2'});
    }]);

    angular.module('fooApp.controllers', []).
            controller('MyCtrl1', [function () {
                console.log("fooApp.MyCtrl1 invoked.");
            }])
            .controller('MyCtrl2', [function () {
                console.log("fooApp.MyCtrl2 invoked.");
            }]);
</script>
</body>
</html>

This is a little less efficient for loading than utilizing a common application base but at the moment this isn't feasible. There is talk at the angular-ui's ui-router team about controlling independent views which may be a workable solution for you but it is currently not implemented, you can follow the discussion at https://github.com/angular-ui/ui-router/issues/84 and chime in with your need. There is also now an issue specifically for this on the ui-router issues list at https://github.com/angular-ui/ui-router/issues/160.

Working plunker of this design: http://plnkr.co/edit/sPoK3I?p=preview

Paul Ryan
  • 1,509
  • 12
  • 26
2

Ok so I figured out how to do this using the angular ui-router the key comes down to the ability of the angular ui-router to transition states without effecting the URL.

The steps to get this working

  • First instantiate each application as a stand alone application using a manual bootstrap to an ID'd element.
  • Attach the ui-router $stateProvider to each application to drive the internal state transitions (routes).
    • You must leave off the url key here for each defined state or you'll reset the page by changing the url on each state transition.
  • Setup a state function in a main controller to drive state changes.

The following is the code to get this working:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.0.x" src="http://code.angularjs.org/1.0.7/angular.min.js" data-semver="1.0.7"></script>
    <script src="angular-ui-states.min.js"></script>
    <script src="app.js"></script>
  </head>
<!-- define foo -->

<div id="fooApp" ng-controller="MainCtrl">

    <ul class="menu">
        <li><a href="#" ng-click="state('foo1')">foo1</a></li>
        <li><a href="#" ng-click="state('foo2')">foo2</a></li>
    </ul>
    <div ui-view>
    </div>

</div>

<script>
    // Declare app level module which depends on filters, and services
    var app = angular.module('fooApp', ['fooApp.controllers', 'ui.state']);

    // Configure the app
    app.config(['$stateProvider', function ($stateProvider) {
        $stateProvider
          .state('foo1',
          {
            template: '<h1>Foo</h1><h2>foo1</h2>', 
            controller: 'MyCtrl1'
          })
          .state('foo2',
          {
            template: '<h1>Foo</h1><h2>foo2</h2>', 
            controller: 'MyCtrl2'
          });
    }]);

    angular.module('fooApp.controllers', [])
            .controller('MainCtrl', ['$scope', '$state', function($scope, $state){
              $scope.state = function(name){
                console.log('Transition to state ' + name);
                $state.transitionTo(name);
              }
            }])
            .controller('MyCtrl1', [function () {
                console.log("fooApp.MyCtrl1 invoked.");
            }])
            .controller('MyCtrl2', [function () {
                console.log("fooApp.MyCtrl2 invoked.");
            }]);

    // manually bootstrap
    var div = document.getElementById('fooApp');
    console.log(div);
    angular.bootstrap(div, ['fooApp']);


</script>


<!-- define bar -->

<div id="barApp" ng-controller="MainCtrl">

    <ul class="menu">
        <li><a href="#" ng-click="state('bar1')">bar1</a></li>
        <li><a href="#" ng-click="state('bar2')">bar2</a></li>
    </ul>
    <div ui-view>
    </div>

</div>

<script>
    // Declare app level module which depends on filters, and services
    var app = angular.module('barApp', ['barApp.controllers', 'ui.state']);


    app.config(['$stateProvider', function ($stateProvider) {
        $stateProvider
          .state('bar1',
          {
            template: '<h1>Bar</h1><h2>bar1</h2>', 
            controller: 'MyCtrl1'
          })
          .state('bar2',
          {
            template: '<h1>Bar</h1><h2>bar2</h2>', 
            controller: 'MyCtrl2'
          });
    }]);

    angular.module('barApp.controllers', [])
            .controller('MainCtrl', ['$scope', '$state', function($scope, $state){
              $scope.state = function(name){
                console.log('Transition to state ' + name);
                $state.transitionTo(name);
              }
            }])
            .controller('MyCtrl1', [function () {
                console.log("barApp.MyCtrl1 invoked.");
            }])
            .controller('MyCtrl2', [function () {
                console.log("barApp.MyCtrl2 invoked.");
            }]);

    // manually bootstrap
    var div = document.getElementById('barApp');
    console.log(div);
    angular.bootstrap(div, ['barApp']);
</script>


</body>

</html>

Working plunker of this solution at http://plnkr.co/edit/bXSN8qSMdioZJLYs2zyk?p=preview

Please see my previous answer for a discussion currently occurring to make portlet support more intrinsic in the ui-router.

Paul Ryan
  • 1,509
  • 12
  • 26
  • This is very interesting. So you use state to change template/controller, and state is scoped to the application. Problem solved. But wait! How do you bookmark! =) – Robert Christian Jun 04 '13 at 22:23
  • What does a bookmark mean in this case? You still have control over the URL through $location but I'm not sure I understand your intent. – Paul Ryan Jun 05 '13 at 04:42
  • Use case is: Aggregating application loads bar application. Within bar you can be at state A, B, or C. User is at state Bar+C, and wants to create a bookmark in the browser. Using state, the URLs for Bar+A, Bar+B, and Bar+C are the same. – Robert Christian Jun 05 '13 at 17:47
  • So your talking about a portlet architecture that updates the url with the component pieces which is currently not natively supported by either the angular or ui-router but is being discussed at https://github.com/angular-ui/ui-router/issues/160. – Paul Ryan Jun 06 '13 at 18:44
1

Figured it out. Here's how to successfully load two angular applications in parallel. Also see that I named the controllers the same for each app to show that dependencies will not collide (since they are scoped within each respective app via module):

<!doctype html>
<html lang="en">

<body>

<script src="lib/angular/angular.js"></script>

<!-- define foo -->

<div id="fooApp">

    <ul class="menu">
        <li><a href="#/foo1">foo1</a></li>
        <li><a href="#/foo2">foo2</a></li>
    </ul>
    <div ng-view>
    </div>

</div>

<script>
    // Declare app level module which depends on filters, and services
    var app = angular.module('fooApp', ['fooApp.controllers']);

    // Configure the app
    app.config(['$routeProvider', function ($routeProvider) {
        $routeProvider.when('/foo1', {template: '<h1>Foo</h1><h2>foo1</h2>', controller: 'MyCtrl1'});
        $routeProvider.when('/foo2', {template: '<h1>Foo</h1><h2>foo2</h2>', controller: 'MyCtrl2'});
    }]);

    angular.module('fooApp.controllers', []).
            controller('MyCtrl1', [function () {
                console.log("fooApp.MyCtrl1 invoked.");
            }])
            .controller('MyCtrl2', [function () {
                console.log("fooApp.MyCtrl2 invoked.");
            }]);

    // manually bootstrap
    var div = document.getElementById('fooApp');
    console.log(div);
    angular.bootstrap(div, ['fooApp']);


</script>


<!-- define bar -->

<div id="barApp">

    <ul class="menu">
        <li><a href="#/bar1">bar1</a></li>
        <li><a href="#/bar2">bar2</a></li>
    </ul>
    <div ng-view>
    </div>

</div>

<script>
    // Declare app level module which depends on filters, and services
    var app = angular.module('barApp', ['barApp.controllers']);

    // Configure the app
    app.config(['$routeProvider', function ($routeProvider) {
        $routeProvider.when('/bar1', {template: '<h1>Bar</h1><h2>bar1</h2>', controller: 'MyCtrl1'});
        $routeProvider.when('/bar2', {template: '<h1>Bar</h1><h2>bar2</h2>', controller: 'MyCtrl2'});
    }]);

    angular.module('barApp.controllers', []).
            controller('MyCtrl1', [function () {
                console.log("barApp.MyCtrl1 invoked.");
            }])
            .controller('MyCtrl2', [function () {
                console.log("barApp.MyCtrl2 invoked.");
            }]);


    // manually bootstrap
    var div = document.getElementById('barApp');
    console.log(div);
    angular.bootstrap(div, ['barApp']);
</script>


</body>

</html>

The only remaining question is how to deal with routing collisions.

Robert Christian
  • 18,218
  • 20
  • 74
  • 89
  • you beat me to it ! Be careful with routing tho.`.otherwise()` would hurt real bad. Also, you may want to use `ready` to bootstrap : `angular.element(document).ready(function () { angular.bootstrap(document, ['app1']); });` – Ven May 31 '13 at 21:18
  • Thanks. The above answer is only the first step. Now I need to answer "how to handle/prevent collisions in routeProvider." – Robert Christian May 31 '13 at 21:24
  • Basically - you don't. I think you're a bit stuck here, angular-ui's router might help with states etc but really, the only thing I can think of is to put one app in html5Mode and the other with html5Mode off. – Ven May 31 '13 at 21:25
  • Another solution is to create a proprietary requirement that routes are expressed external to config as a JSON. The aggregating app, prior to manual bootstrapping, can examine all json and fast fail on collision. And by convention the "sub apps" could call a utility method within config that iterates over their routes json and sets routes appropriately. It's not much effort codewise, and a pretty simple convention to follow. – Robert Christian May 31 '13 at 21:40
  • At this point, I am willing to award the bounty to anyone who has a better solution than what's outlined in the above comments (since S/O doesn't let you award yourself the bounty in the event that you answered your own question). – Robert Christian May 31 '13 at 21:48
  • I don't have anything more to add I'm afraid :-). – Ven May 31 '13 at 22:17
0

Well you have 2 choices here: if you create them as angular.module() there would not be a way atm to connect the modules with each other.

if you create directives with a templateURL to lazyload your components you could broadcast shared attributes and listen to them and you could use the same services in your app. Probably that would be the best for you.

Heinrich
  • 313
  • 2
  • 13
  • The answer is going to be closer to this http://stackoverflow.com/questions/12860595/how-to-define-two-angular-apps-modules-in-one-page. – Robert Christian May 31 '13 at 20:12