Small proof-of-concept (POC) prototype for bootstrapping multiple Angular 2 apps on one page and establish a communication between those apps.
JavaScript TypeScript HTML CSS
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
App-One
App-Two
ng-shared
.gitignore
Dockerfile
LICENSE
README.md
docker-compose.yml
favicon.ico
index.html
package.json
systemjs.config.js

README.md

license

CAUTION: This is not the preferred way to setup some widgets on a multi page environment!

Multiple ng2 apps on one page

This project is one proof-of-concept prototype corresponding to the novatec blog post:

http://blog.novatec-gmbh.de/angular-2-in-a-multi-page-application/

Please read the blog in order to get a better understanding why and what's definetly better than the described prototype here.

Install and Run the POC

Docker(-Compose), Nodejs and NPM are required.

In order to get started just type npm install. In order to get the Docker container up and running, you first have to build (npm run build) the apps and shared-services. Finally type docker-compose up --build into your console and browse to http://localhost:8080

Background of the repo

A few weeks ago we dealed with the question, if it is possible to bootstrap multiple Angular 2 Apps on one page and if so, would we have to consider special consequences. Therefore we tried to set up a small proof-of-concept (POC) prototype. And in the case it would work, we wanted to figure out how to create a communication channel between these apps.

Multiple Angular 2 apps on one page?

But first things first. In our prototype we used the Angular Quickstart project for two POC-apps. This is a very fast way to get a simple setup of the NG2 environment. After git cloning and npm installing everything, we made some minor changes as these apps should be bootstrapped on the same page:

@Component({
  selector: 'my-app-one',
  template: '<h1>Hello my name is {{myname}}</h1>',
})
export class AppComponent  { 
  myname = 'App One';
}

As a result we renamed the component selectors of the apps (in index.html and app.component.ts). Furthermore we adjusted the output in order to set app one apart from app two, so that one can distinguish between those apps

Set up the integration layer

The next step was to create the integration layer. This layer is the skeleton of a minimal serve environment for an Angular app. We used a Dockerfile with nginx as basis for the web server to include only the necessary resources and show that it works. With systemjs (which is used by the angular quickstart project), you have to consider some changes which have to be made, in order to import and bootstrap both apps successfully on one page:

  1. We prepared a new index.html, which includes both apps, not only one:

    <head>
      <!-- other head parts -->
      <script>
        System.import('appone').catch(function(e){ console.error(e); });
        System.import('apptwo').catch(function(e){ console.error(e); });
      </script>
    </head>
    
    <body>
      <my-app-one>Loading App One content here ...</my-app-one>
      <my-app-two>Loading App Two content here ...</my-app-two>
    </body>
  2. Then we configured the mapping of appone and apptwo systemjs.config.js according to the paths of both apps in the Dockerfile.

  3. Moreover a common package.json has to be prepared. It is used to install the node-modules, which then can be served by the nginx in the docker image and imported by systemjs for both apps.

We started the docker container and opened the browser (http://localhost:8080): Nice, both apps got bootstrapped successfully on the same page!

Successful bootstrapping two ng2 apps on the same page

Successful bootstrapping two ng2 apps on the same page

Communication between both apps?

Everything good so far. But what is about establishing a communication channel between both apps? As one can imagine, it would be nice to have the opportunity for the apps to notify each other of some actions and changes that happened inside of themselves. For this requirement a publish/subscribe service can be a good way to go. Because of this we decided to implement a simple publish/subscribe service which then should be used by both apps.

import { Injectable } from '@angular/core';
import * as Rx from 'rxjs/Rx';

@Injectable()
export class SharedService {
  private subjects: any;

  constructor() {
    this.subjects = {};
  }

  public publish(name: string, data: any): void {
    let fnName = this.createName(name);
    this.subjects[fnName] || (this.subjects[fnName] = new Rx.ReplaySubject(1));
    this.subjects[fnName].next(data);
  }

  public subscribe(name: string, handler: (data: any) => void): Rx.ReplaySubject<any> {
    let fnName = this.createName(name);
    this.subjects[fnName] || (this.subjects[fnName] = new Rx.ReplaySubject(1));
    return this.subjects[fnName].subscribe(handler);
  }

  private createName(name: string): string {
    return '$' + name;
  }
}

let instance: SharedService = null;

export function SharedServiceFactory() {
  if (instance == null) {
    instance = new SharedService();
  }
  return instance;
}

This service uses the typical ng2 Injectable decorator. We exported the service as a private node package "ng2-shared". Consequently the service can be easily imported and used in any Angular project. Therefor it has to be included in the node-modules directory of both apps as well as in the integrators. (Just a hint: You can use 'npm install' in order to get everything together, if you clone the git repo linked at the bottom of this page.)

How to use in Angular?

The apps can then use this service and publish/subscribe on specific topics. But how do they use it? The crux here is, that the apps shouldn't create two different instances of this service. Instead we wanted to go for one shared instance. Thus we created the SharedServiceFactory in order to provide one singleton instance of this service. The reason why it is named a "factory", is that we had to provide this service to the Angular 2 apps. In the app.module.ts it is included in sense of a factory class, in order to get only one instance running, even if there are multiple apps and components injecting this class:

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SharedService, SharedServiceFactory } from 'ng2-shared/service';

import { AppComponent }  from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ],
  providers: [{ provide: SharedService,
    useFactory: SharedServiceFactory}]
})
export class AppModule { }

That's it basically. After we created some basic usage examples for both apps, we ran them with docker and everything worked fine:

Communication output between multiple ng2 apps

Successful communication: Output of multiple ng2 apps

Conclusion

In conclusion: What can we expect from that?

Benefits

  • Independent development of multiple apps is possible. Basically two teams can develop two apps independent from each other.
  • Communication in a loosely coupled way. The apps don't call function directly in sense of a remote procedure call. the communication takes part in a publish/subscribe way.
  • Loads the dependencies only once. Because we created an integration layer which serves the dependencies (modules) via systemjs on the same page, the packages are only loaded once. Not twice (for each).

Downsides

Noteworthy, as also stated in the angular code:

```
When working within a browser window, there are many singleton resources: cookies, title, location, and others. Angular services that represent these resources must likewise be shared across all Angular applications that occupy the same browser window.
```

Consequently the app uses some browser resources which are (like the shared service) shared between all apps. This has to be considered, as it causes conflicts if both use the _@angular/router_, for example.

The Angular and dependency versions used by the apps have to be the same. As the dependencies are loaded in the same browser window different versions of the same dependency will cause conflicts. This limits the independent development mentioned before somehow.

Caution

This is not the preferred way to setup some widgets on a multi page environment! Please read the blog in order to get a better understanding why and what's definetly better than the described prototype here.