Embedding Angular Components into a Legacy Web App
Tags: angular, webdev, javascript, frontend
Published: 2018-07-25 20:36:57

---
title: Embedding Angular Components into a Legacy Web App
published: true
description: 
tags: angular,webdev,javascript,frontend
---

Original post: [Embedding Angular Components into a Legacy Web App](http://bobonmedicaldevicesoftware.com/blog/2018/07/25/embedding-angular-components-into-a-legacy-web-app) 

_Note_: Code samples do not include line numbers in this version.

<img class="alignright size-thumbnail wp-image-846" src="http://bobonmedicaldevicesoftware.com/blog/wp-content/uploads/2018/07/embedded-angular-150x150.png" alt="" width="150" height="150" /> In a perfect world, you'd be able to create a greenfield Angular SPA from scratch. In the real world, that's usually not the case. That legacy web application has way too much baggage to realistically convert it to an SPA in a single shot. This is particularly true if you're currently using server-side rendering with (e.g.) JSP or Rails technology.

The only real solution is to <strong>incrementally</strong> move/upgrade pieces of UI logic and data access patterns (i.e. converting to REST interfaces). If you are planning a move to Angular<strong>*</strong>, a good starting point is to first embed small pieces of Angular-implemented logic into your existing application. This approach also allows the new Angular components to share CSS styles for seamless visual integration.

<strong>NgInterop</strong> is a simple TypeScript class that allows a legacy Web application to have <strong>two-way communications</strong> (via <a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern" target="_blank" rel="noopener">pub/sub</a>) with embedded Angular components. The underlying <strong>MessagingSerivce</strong> class is an implementation of <a href="http://www.processinginfinity.com/weblog/2016/08/18/MessageBus-Pattern-in-Angular2-TypeScript" target="_blank" rel="noopener">Message Bus pattern in Angular 2 and TypeScript</a>.

Source code for the demo project is here: <a href="https://github.com/rnadler/embedded-angular" target="_blank" rel="noopener">embedded-angular</a>

**NgInterop.ts**
```javascript 
import {Injectable, NgZone} from '@angular/core';
import {AngularEvent, EventCallbackFunction, HtmlEvent, LogEvent} from './event.types';
import {MessagingService} from './messaging.service';

@Injectable({
  providedIn: 'root'
})
export class NgInterop {
  public static readonly ANGULAR_EVENT: string = 'AngularEvent';
  public static readonly HTML_EVENT: string = 'HtmlEvent';
  public static readonly LOG_EVENT: string = 'LogEvent';
  private typeClassMap: any = {};
  private readonly initCallback: any;

  constructor(private ngZone: NgZone, private messagingService: MessagingService) {
    this.typeClassMap[NgInterop.ANGULAR_EVENT] =  AngularEvent;
    this.typeClassMap[NgInterop.HTML_EVENT] =  HtmlEvent;
    this.typeClassMap[NgInterop.LOG_EVENT] =  LogEvent;
    this.initCallback = window['NgInteropInitCallback'];
    window['ngInterop'] = this;
    this.init();
  }
  private init() {
    if (!this.initCallback) {
      console.warn('NgInterop.init: No NgInteropInitCallback found!');
      return;
    }
    this.initCallback();
  }
  public subscribeToClass(className: string, callBack: EventCallbackFunction): any {
    const self = this;
    this.ngZone.run(() => {
      self.messagingService.of(self.typeClassMap[className]).subscribe(callBack);
    });
  }
  public publishToClass(className: string, source: string, value: string): any {
    const self = this;
    this.ngZone.run(() => {
      self.messagingService.publish(new self.typeClassMap[className](source, value));
    });
  }
}
```

Highlights:

<ul>
 	<li><strong>6</strong>: Side note on the new Angular 6 <strong>providedIn</strong> syntax. This saves from having to add every service to the app.module.ts @NgModule providers list. Very handy!</li>
 	<li><strong>19</strong>: This saves the native JavaScript initialization callback function (see index.html below). This example only has one callback function, but it would be easy to extend this functionality to support multiple initialization callbacks.</li>
 	<li><strong>20</strong>: Add the NgInterop instance into the <code>window</code> object so that external JavaScript can simply call methods on <code>window.ngInterop</code> (again, see index.html below).</li>
 	<li><strong>32 and 38</strong>: Wrap the MessagingService subscribe/publish in a <a href="https://angular.io/api/core/NgZone#run" target="_blank" rel="noopener">NgZone.run()</a> call. This allows the external JavaScript to execute these functions in the Angular zone.</li>
</ul>

Other notes:

<ul>
 	<li>The <strong>typeClassMap</strong> object maps a BaseEvent class name (string) to a real class. The public static *_EVENT names provide safer access to the NgInterop functions from the Angular code.</li>
 	<li>There's no way to do type or parameter checking on the native JavaScript side, but it is still good practice to strongly type the BaseEvent derived classes. This provides good documentation and catches problems early on the TypeScript side.</li>
</ul>

Here is the stripped down <strong>index.html</strong> that shows how the external JavaScript code interacts with NgInterop.

**index.html (snippet)**

```javascript
<script>
  function subscribeToEvents() {
    ...
    window.ngInterop.subscribeToClass('AngularEvent', function (event) {
     ...
    });
  }
  ...
  function clickMe() {
    window.ngInterop.publishToClass('HtmlEvent', 'clickMe', getRandomString());
  }
  ...
  window['NgInteropInitCallback'] = subscribeToEvents;
</script>
```
Highlights:
<ul>
 	<li><strong>4</strong>: After <strong>subscribeToEvents()</strong> is called by the NgInterop constructor, this function subscribes to AngularEvent messages. AngularEvent messages are published when the Angular 'Toggle Remove Button' is clicked in the <strong>AppComponent</strong> class.</li>
 	<li><strong>10</strong>: On an HTML click event an HtmlEvent message is published. The subscriber to the HtmlEvent messages is also in the AppComponent class.</li>
 	<li><strong>13</strong>: The callback function is added to the <code>window</code> object. This is executed prior to Angular being started up.</li>
 	<li>All logging is done by publishing LogEvent messages. These are displayed by the <strong>LogComponent</strong> class.</li>
</ul>
The example app has two Angular components that interact with the native JavaScript as well as with each other with NgInterop. The rest of the code should be self-explanatory.

Screenshot of the example app:

<a href="https://github.com/rnadler/embedded-angular" target="_blank" rel="noopener"><img class="aligncenter wp-image-847" src="http://bobonmedicaldevicesoftware.com/blog/wp-content/uploads/2018/07/embedded-angular-app.png" alt="" width="452" height="323" /></a>
This project uses the following:
<ul>
 	<li><a href="https://github.com/angular/angular-cli" target="_blank" rel="noopener">Angular CLI</a> -- Of course.</li>
 	<li><a href="https://rxjs-dev.firebaseapp.com/" target="_blank" rel="noopener">RxJS</a>  -- Used by the MessagingService.</li>
 	<li><a href="https://getbootstrap.com/" target="_blank" rel="noopener">Bootstrap 4</a> -- For the pretty buttons and "card" layout.</li>
 	<li><a href="https://momentjs.com/" target="_blank" rel="noopener">Moment.js</a>  -- To more easily format the log timestamp.</li>
 	<li><a href="https://www.protractortest.org/#/" target="_blank" rel="noopener">Protractor</a>  -- For running the Angular e2e tests.</li>
</ul>
Enjoy!
<strong>*</strong>There are probably similar integration approaches for <a href="https://reactjs.org/" target="_blank" rel="noopener">React</a> and <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue</a>. I just don't know what they are.