29

I am currently working on porting a Backbone project to an Angular 2 project (obviously with a lot of changes), and one of the project requirements requires certain methods to be accessible publicly.

A quick example:

Component

@component({...})
class MyTest {
    private text:string = '';
    public setText(text:string) {
        this.text = text;
    }
}

Obviously, I could have <button (click)="setText('hello world')>Click me!</button>, and I would want to do that as well. However, I'd like to be able to access it publicly.

Like this

<button onclick="angular.MyTest.setText('Hello from outside angular!')"></click>

Or this

// in the js console
angular.MyTest.setText('Hello from outside angular!');

Either way, I would like the method to be publicly exposed so it can be called from outside the angular 2 app.

This is something we've done in backbone, but I guess my Google foo isn't strong enough to find a good solution for this using angular.

We would prefer to only expose some methods and have a list of public apis, so if you have tips for doing that as well, it'd be an added bonus. (I have ideas, but others are welcomed.)

2

5 Answers 5

29

Just make the component register itself in a global map and you can access it from there.

Use either the constructor or ngOnInit() or any of the other lifecycle hooks to register the component and ngOnDestroy() to unregister it.

When you call Angular methods from outside Angular, Angular doesn't recognize model change. This is what Angulars NgZone is for. To get a reference to Angular zone just inject it to the constructor

constructor(zone:NgZone) {
}

You can either make zone itself available in a global object as well or just execute the code inside the component within the zone.

For example

calledFromOutside(newValue:String) {
  this.zone.run(() => {
    this.value = newValue;
  });
}

or use the global zone reference like

zone.run(() => { component.calledFromOutside(newValue); });

https://plnkr.co/edit/6gv2MbT4yzUhVUfv5u1b?p=preview

In the browser console you have to switch from <topframe> to plunkerPreviewTarget.... because Plunker executes the code in an iFrame. Then run

window.angularComponentRef.zone.run(() => {window.angularComponentRef.component.callFromOutside('1');})

or

window.angularComponentRef.zone.run(() => {window.angularComponentRef.componentFn('2');})
Sign up to request clarification or add additional context in comments.

5 Comments

Ok, so I'll need to make helper methods for all my public methods. Thanks a lot for your help, btw.
So, in creating a service that registers tasks, I'm having the same issue as I was before I started using zone.run(()=>{}) The method is registered, and I can call it without issue, however, it seems like the change detection is not being run. window.widget[name] = () => { window.zone.run(() => { scope[callbackMethod](); }); }; scope above is the component, and the method I'm calling is the callbackMethod. Any tips?
I appreciate the plunkr, but the issue is that I have an external service that is adding the method on a window.widget object. I can call the methods, but the change detection isn't firing. It looks like your example contains what I was using when I wasn't using an external service. (I may just scrap that external service.) Thanks for the help.
now angular not excepting razor pages so ngZone not work for this scenario right? else my assumption is wrong?
@k11k2 I don't know what razor pages are or how they could be related to ngZone
23

This is how i did it. My component is given below. Don't forget to import NgZone. It is the most important part here. It's NgZone that lets angular understand outside external context. Running functions via zone allows you to reenter Angular zone from a task that was executed outside of the Angular zone. We need it here since we are dealing with an outside call that's not in angular zone.

 import { Component, Input , NgZone } from '@angular/core';
 import { Router } from '@angular/router';

    @Component({
        selector: 'example',
        templateUrl: './example.html',
    })
    export class ExampleComponent {
            public constructor(private zone: NgZone, private router: Router) {

//exposing component to the outside here
//componentFn called from outside and it in return calls callExampleFunction()
        window['angularComponentReference'] = {
            zone: this.zone,
            componentFn: (value) => this.callExampleFunction(value),
            component: this,
        };
    }

    public callExampleFunction(value: any): any {
        console.log('this works perfect');
        }
    }

now lets call this from outside.in my case i wanted to reach here through the script tags of my index.html.my index.html is given below.

<script>

//my listener to outside clicks
ipc.on('send-click-to-AT', (evt, entitlement) => 
electronClick(entitlement));;

//function invoked upon the outside click event

 function electronClick(entitlement){
//this is the important part.call the exposed function inside angular 
//component

    window.angularComponentReference.zone.run(() =
    {window.angularComponentReference.componentFn(entitlement);});
 }
</script>

if you just type the below in developer console and hit enter it will invoke the exposed method and 'this works perfect ' will be printed on console.

 window.angularComponentReference.zone.run(() =>
{window.angularComponentReference.componentFn(1);});

entitlement is just some value that is passed here as a parameter.

6 Comments

I just followed your steps end up with Cannot read property 'zone' of undefined this occurs at ClickEvent in home.cshtml my function looks like <script> function ClickEvent() { window.angularComponentReference.zone.run(() => { window.angularComponentReference.component.ClickEvents(); }); } </script> ps: In developer console it works fine.
did u import Ngzone to your component ?
BE CAREFUL !! Exposing a function outside angular with () => {} will not work with IE. It will not be transpiled by Angular. You should use function() { }
I have upvoted because your answer is much clearer than the accepted one IMHO
Why do I need zone, I can directly call window.angularComponentReference.componentFn(entitlement). It worked?
|
4

I was checking the code, and I have faced that the Zone is not probably necessary. It works well without the NgZone.

In component constructor do this:

constructor(....) {
   window['fncIdentifierCompRef'] = {
      component  = this
   };
}

And in the root script try this:

<script>
function theGlobalJavascriptFnc(value) {
  try {
    if (!window.fncIdentifierCompRef) {
      alert('No window.fncIdentifierCompRef');
      return;
    }
    if (!window.fncIdentifierCompRef.component) {
      alert('No window.fncIdentifierCompRef.component');
      return;
    }  
    window.fncIdentifierCompRef.component.PublicCmpFunc(value);
  } catch(ex) {alert('Error on Cmp.PublicCmpFunc Method Call')}  
}
</script>

This works to me.

Comments

1

The problem is that Angular's components are transpiled into modules that aren't as easy to access as regular JavaScript code. The process of accessing a module's features depends on the module's format.

An Angular2 class can contain static members that can be defined without instantiating a new object. You might want to change your code to something like:

@component({...})
class MyTest {
    private static text: string = '';
    public static setText(text:string) {
        this.text = text;
    }
}

3 Comments

Wouldn't I still need a way to expose the component publicly?
To access a component outside of Angular, you should look at the transpiled JavaScript. In general, web pages load components by accessing a loader (see System.import at SystemJS).
The components loaded via systemjs are not accessible by default, looking at the transpiled js doesnt help either as the js is loading using system.register. Angular (or angular, etc) is not even available in the global scope, (stackoverflow.com/questions/34779126/…) and we won't be using systemjs in production.
0

Super simple solution!! save component or function with an alias outside

declare var exposedFunction;
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
    constructor(public service:MyService){
    exposedFunction = service.myFunction;
}

at index.html add in head

<script>
    var exposedFunction;
</script>

Inside exposed function do not use this. parameters if you need them you will have to use closures to get it to work

This is particularly useful in ionic to test device notifications on web instead of device

2 Comments

so how to call it from android?
The same way, just add that in your web files @MohammadRezaMrg, index.html and your exposed function

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.