Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

NgUpgrade Pub Sub in Angular

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Fri Mar 10 2017

In the following post I will show how to implement pub/sub in an NgUpgrade application.

NgUpgrade is the bridge between the old world and the new world. Basically it allows code from Angular 1.x to live in the same app as code written in newer versions of Angular.

In my view, the recommended approach when using NgUpgrade is to downgrade new Angular code and embed it in existing Angular 1.x code. Technically, you can also go in the opposite direction by upgrading Angular 1.x code. However, I see this as somewhat counter productive if the goal is to eventually phase out the Angular 1.x code.

It's important to point out that the downgrade process does not convert your code to Angular 1.x code. The “downgraded” code will still execute in the context of new Angular. This is true for both components and services.

In the following example I will show how to implement RxJs based Pub/Sub between a legacy Angular 1.x component and a downgraded Angular 2+ component.

The key to this is exercise using a downgraded Angular 2+ service. Registering the service as an NgModule level provider ensures that both components will share the same service reference. Sharing the same instance is important since it allows for shared state.

Service

First lets take a look at the shared service.

Import { Subject } from 'rxjs/Subject'; import 'rxjs/add/operator/filter'; export class UpgradePubSubService { constructor() { console.log('UpgradePubSubService created'); } private messageSubject = new Subject<any>(); sendMessage(msg: string, key: string) { this.messageSubject.next({message: msg, key: key}); } getMessages() { return this.messageSubject.asObservable(); } }

If you are familiar with RxJs you will recognize this as a typical Subject based Pub/Sub service.

There is no trace of Angular 1.x here since this service will in all cases execute in the context of Angular 2+. Even when it's embedded in the Angular 1.x component.

Angular 2+ Component

The Angular 2+ component is also pretty standard. It injects an instance of the Pub/Sub service.

import {Component} from '@angular/core'; import {UpgradePubSubService} from './upgrade-pub-sub.service'; @Component({ template: `<div>{{message}}</div> <button (click)="sendMessage()">Send Message From New Angular</button>` }) export class MessageComponent { message: string; constructor(private service: UpgradePubSubService) {} ngOnInit() { this.service.getMessages() .filter(m => m.key === 'old') .subscribe((data) => { this.message = data.message; }); } sendMessage() { this.service.sendMessage('Hello old Angular! This is new Angular', 'new'); } }

Angular 1.x Component

Next up is the controller of the Angular 1.x component.

angular.module('angular-legacy').controller('testController', function(messageService){ var vm = this; this.newMessage = ''; this.sendMessage = function() { messageService.sendMessage('Hello new Angular! This is Old Angular', 'old'); }; messageService.getMessages() .filter(function(data) { return data.key === 'new'; }) .subscribe(function(data) { vm.newMessage = data.message; }); });

As you can tell, the same Pub/Sub service is injected into the legacy component. How does Angular know to inject the service into the old Angular world?

This is where NgUpgrade works its magic.

You downgrade the service by running the following NgUpgrade code:

import { downgradeInjectable } from '@angular/upgrade/static'; import { UpgradePubSubService } from './upgrade-pub-sub.service'; angular.module('angular1').service('messageService', downgradeInjectable(UpgradePubSubService));

Pretty cool that we can make the service compatible with Angular 1.x with just a few lines of code, right?

Exchanging Messages

The service now exits in both Angular worlds, but it's important to remember that it only executes in the context of the new world. This makes state sharing much easier since we are not dealing with two different instances.

If you emit a message from one component, it will be picked up by the other component. Basically the components subscribe to each others messages. I added a filter to ensure that the components don't react to their own messages.

If you register the service as a provider at the NgModule level it will be created as a singleton across both worlds. In both cases we use DI to inject the service, but the same instance is injected in both components.

Keep in mind you will break the sharing if you register the service at the component level in the Angular 2+ component though.

Demo

I have put a demo up on my site if you want to check it out. The code can be found on Github.

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter