3

I'd like to be able to pass some data\propagate events from a plugin on the page to my Angular 4 app.

More specifically, in my case data\events are generated inside a Silverlight plugin app that is next to the Angular app on the page.

I have the following solution in my mind:

  • Create a global JS function which gets called from Silverlight (since this seems to be the simplest way to get data out from Silverlight) when there is a need to talk to Angular side.
  • The function, in turn, calls some Angular class method passing data collected from Silverlight.

As an illustration to that (excluding the Silverlight part), we could have the following.

A method as an entry point on the Angular side:

export class SomeAngularClass {
    public method(data: any): void {
        ...
    }
}

And somewhere outside the Angular realm, we add a global plain JavaScript function (to be called by Silverlight):

window.somePlainJsFunction = function (data) {
    // How to consume SomeAngularClass.method() from here?
}

The question is: how can we call the Angular class methods from a plain JavaScript function?

Alexander Abakumov
  • 10,817
  • 10
  • 71
  • 111
  • One way of doing this could be having your JavaScript send a event and then your service (or a angular directive) listen for that event. – Dumpen Sep 08 '17 at 16:43
  • 1
    *I know that Angular exposes its API through a global JavaScript object* - it doesn't. AngularJS did. The actual solution depends on what somePlainJsFunction is and where it's used. Unless there are really good reasons to keep it global, somePlainJsFunction should be defined and called inside Angular application, not in the opposite way. – Estus Flask Sep 08 '17 at 17:48
  • Possible duplicate of [How to share service between two modules - @NgModule in angular2?](https://stackoverflow.com/questions/40089316/how-to-share-service-between-two-modules-ngmodule-in-angular2) – Aniruddha Das Sep 08 '17 at 18:47
  • @estus: Actually, it does. See, for instance [here](https://alfonsopresa.wordpress.com/2015/08/29/creating-angular-2-applications-with-plain-old-javascript-es5/). – Alexander Abakumov Sep 08 '17 at 19:02
  • I'm aware of this. This happens when Angular is loaded as UMD module in – Estus Flask Sep 08 '17 at 19:05
  • @estus: That's interesting. Then this is weird that when using Angular via Typescript\modules there is no any API entry point. – Alexander Abakumov Sep 08 '17 at 19:11
  • @AlexanderAbakumov, what kind of integration do you need? Angular doesn't expose any global object in production mode – Max Koretskyi Sep 08 '17 at 19:17
  • @AngularInDepth.com: Generally speaking, I just need to be able to call some Angular-side method passing some parameter(s). More specifically, this is needed to push data/propagate events from Silverlight to Angular. – Alexander Abakumov Sep 08 '17 at 19:23
  • @AlexanderAbakumov, where exactly _to Angular_? – Max Koretskyi Sep 08 '17 at 19:25
  • @AngularInDepth.com: Sorry, not sure what you mean exactly. I have a page with an Angular controlled area as well as a Silverlight object in it. – Alexander Abakumov Sep 08 '17 at 19:28
  • @AlexanderAbakumov, _to call some Angular-side method_ - on what class? what method? – Max Koretskyi Sep 08 '17 at 19:29
  • @AngularInDepth.com: Oh, currently it's just a service (@Injectable) class containing a method with a few arguments to collect data from the Silverlight side. This is just because it seemed to me as the simplest way. – Alexander Abakumov Sep 08 '17 at 19:34
  • There's nothing weird. `ng` global is a fallback for the ones who can't build the app and does it the old way. Angular is opinionated and expects that all relevant code will be built within app bundle. – Estus Flask Sep 08 '17 at 19:40
  • @estus: It's weird since we kind of used to at least have a choice to have access to a framework API via a global object in JS world :) Anyway, thanks for pointing that it's impossible with Angular + TypeScript. – Alexander Abakumov Sep 08 '17 at 19:41
  • As it was said above, If you need to interact with external code this should be done in the opposite way. A callback should be provided to global `window.someFunction` from inside the app. If it's possible to get access to Silverlight element from inside the app and interact with it, this is the preferable way to do this. Please, provide all necessary info to reflect your real case, see http://stackoverflow.com/help/mcve . You have XY problem with this approach. – Estus Flask Sep 08 '17 at 19:45
  • This was possible in A1. In A2+ it's different. It's possible to expose [an injector via a hack](https://stackoverflow.com/questions/39409328/storing-injector-instance-for-use-in-components) on `window`, but this is totally wrong way to do this which shouldn't be suggested in the answer. Because it's wrong. – Estus Flask Sep 08 '17 at 19:47
  • @estus: `If it's possible to get access to Silverlight element from inside the app and interact with it, this is the preferable way to do this.` I need that Silverlight app kicks the process. For this, Silverlight is able to call a global JS function. This is what I described in the initial question (edited it for more clarity, by the way). Now I see that I need some preliminary wiring, since we can't call Angular from the plain JS. Got it, thank you! – Alexander Abakumov Sep 08 '17 at 20:16
  • @estus: Yes, could you please add your idea (`A callback should be provided to global window.someFunction from inside the app.`) as an answer? I initially thought it should be something like the [@AniruddhaDas implementation](https://stackoverflow.com/a/46122828/3345644) solution, but I seemed to be wrong again. I'm new to TypeScript\Angular 4 and can't bind providing global callback and calling a TypeScript class method together. – Alexander Abakumov Sep 08 '17 at 20:33
  • This depends on how things are done in your app. If `somePlainJsFunction` is called by Silverlight before Angular app has been bootstrapped, none of suggested approaches will work. Otherwise it's enough to do `window.somePlainJsFunction = ...` *inside* Angular app. – Estus Flask Sep 08 '17 at 20:43

4 Answers4

7

As pointed by @Dumpen, you can use @HostListener to get the custom event dispatched from javascript outside of Angular. If you also want to send parameters, then you can send them by adding them as detail object.

In Javascript:

function dispatch(email, password) {
    var event = new CustomEvent('onLogin', {
        detail: {
            email: email,
            password: password
        }
    })
    window.dispatchEvent(event);
}

Then you can call this method on button click.

Now to listen to this dispatched event in Angular, you can create function in Angular component and add @HostListener to it like below:

@HostListener('window:onLogin', ['$event.detail'])
onLogin(detail) {
    console.log('login', detail);
}

This code you can add in any component. I have added it to my root component - AppComponent

Pranit More
  • 376
  • 3
  • 11
2

You can use a events, so you have the JavaScript send an event that you are listening for in your service.

The CustomEvent is a new feature so you might need to polyfill it: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent

JavaScript:

var event = new CustomEvent("CallAngularService");

window.dispatchEvent(event);

Angular:

@HostListener("window:CallAngularService")
onCallAngularService() {
  // Run your service call here
}
Dumpen
  • 1,540
  • 5
  • 23
  • 34
1

I think this is what you are looking for service out side angular

You can create function/class outside Angular and provide as a value in the angular. in this way you can handle both angular and non angular stuffs together:

class SomePlainClass {
  ...
}
window.somePlainJsFunction = new SomePlainClass();

@NgModule({
  providers: [{provide: SomePlainClass, useValue: window.somePlainJsFunction}],
  ...
})
class AppModule1 {}

@NgModule({
  providers: [{provide: SomePlainClass, useValue: window.somePlainJsFunction}],
  ...
})
class AppModule2 {}

class MyComponent {
  constructor(private zone:NgZone, private SomePlainClass:SharedService) {
    SomePlainClass.someObservable.subscribe(data => this.zone.run(() => {
      // event handler code here
    }));
  }
}
Aniruddha Das
  • 14,517
  • 13
  • 83
  • 111
  • It looks like a pretty decent option for some situation. Unfortunately in my case, I have to expose some "static" (in terms of TypeScript) method for Silverlight, since it can't call anything instance-like. Anyway, thanks for your input! – Alexander Abakumov Sep 12 '17 at 09:28
0

The way it should be done depends on particular case, especially on the precedence.

If Silverlight aplication is initialized before Angular application, and window.somePlainJsFunction is called before finishing Angular application initialization, this will result in race condition. Even if there was an acceptable way to get Angular provider instance externally, the instance wouldn't exist during somePlainJsFunction call.

If window.somePlainJsFunction callback is called after Angular bootstrap, window.somePlainJsFunction should be assigned inside Angular application, where SomeAngularClass provider instance is reachable:

window.somePlainJsFunction = function (data) {
    SomeAngularClass.method();
}

If window.somePlainJsFunction callback is called before Angular bootstrap, it should provide global data for Angular application to pick up:

window.somePlainJsFunction = function (data) {
    window.angularGlobalData = data;
}

Then window.angularGlobalData can be used inside Angular, either directly or as a provider.

Working Example

Pardeep Jain
  • 71,130
  • 29
  • 141
  • 199
Estus Flask
  • 150,909
  • 47
  • 291
  • 441