How to communicate with Angular components outside of Angular
I recently had to figure out how to interact with Angular components from outside of Angular, from plain JavaScript rendered to a page. In my case, I was opening a modal dialog written in Angular from a traditional server-side rendered page.
I imagine I am not the only one who needs to mix Angular code with other code on the page, so I want to share a code that will allow you to do that easily. I based it on a publish-subscribe pattern to separate the code in a nice and clean way.
The primary use-cases are:
- Manipulate the original web page from the Angular component without putting DOM manipulation code directly into a component.
- Call any of our Angular code from the basic <script> tag on the page.
I wrote it as an Angular service, named GlobalPubSub
. This service must be part of the Angular module that you load on the page. It will then attach functions on the global window object that you can call outside of Angular.
Basic usage:
- Inside of your Angular components, subscribe to be notified with
globalPubSub.subcribe
method and send messages from outside utilizing the globalwindow.fireAngularEvent
function, passing any number of arguments - Outside of Angular, subscribe to be notified with global function
window.subscribeToAngularEvent
and send messages from Angular by callingglobalPubSub.fireEvent
A few notes on implementation:
- The example is written in TypeScript but can be rewritten in JavaScript.
- I decided to have a list of allowed events so that all events have to be defined in the component to improve code maintainability.
- To run Angular code from outside Angular, the code has to be called inside the so-called Angular zone (otherwise, strange things would be happening). It took me some time to figure it out!
- You can rename the methods to make it a bit more general. I wanted to clarify when I fire Angular events or events from Angular.
- The internal, basic implementation of the publish-subscribe pattern can be, of course, rewritten. One of the options would be to use RxJS.
Here it is, let me know what you think!
import {Injectable, NgZone} from "@angular/core";
/**
* Service that allows Angular components to receive and fire
* events from outside
*
* Usage from outside of Angular:
* window.fireAngularEvent('sampleEventName', args)
* window.subscribeToAngularEvent('sampleEventName', fn)
*
* Usage from Angular component:
* globalPubSub.fireEvent('sampleEventName', args)
* globalPubSub.subscribe('sampleEventName', fn)
*/
@Injectable()
export class GlobalPubSub {
allowedEvents = [
"sampleEventName",
"sampleEventName2"
];
private subscriptions : {[key:string]:Function[];} = {};
constructor(private zone: NgZone) {
this.allowedEvents.forEach((eventName) => {
this.subscriptions[eventName] = []
});
window['fireAngularEvent'] = (eventName, args) => {
if (!this.subscriptions[eventName]) {
throw new Error('Event has to be defined in the event list.')
}
zone.run(() => {
this.fireEvent(eventName, args);
});
};
window['subscribeToAngularEvent'] = (eventName, fn) => {
this.subscribe(eventName, fn);
};
}
subscribe(eventName: string, fn: Function) {
if (!this.subscriptions[eventName]) {
throw new Error('Event has to be defined in the event list.');
}
this.subscriptions[eventName].push(fn);
}
fireEvent(eventName: string, args) {
if (!this.subscriptions[eventName]) {
throw new Error('Event has to be defined in the event list.');
}
this.subscriptions[eventName].forEach((fn) => {
fn.apply(null, args);
});
}
}
Last updated on 8.3.2022.