Angular2 - how to call component function from outside the app

Angular

Angular Problem Overview


I am using a javascript Object that has a callback. Once the callback is fired I want to call a function inside an Angular2 component.

example HTML file.

    var run = new Hello('callbackfunction');
    
    function callbackfunction(){   
     // how to call the function **runThisFunctionFromOutside**
   }
   <script>
      System.config({
        transpiler: 'typescript', 
        typescriptOptions: { emitDecoratorMetadata: true }, 
        packages: {'js/app': {defaultExtension: 'ts'}} 
      });
      System.import('js/app/main')
            .then(null, console.error.bind(console));
    </script>

My App.component.ts

import {Component NgZone} from 'angular2/core';
import {GameButtonsComponent} from './buttons/game-buttons.component';
@Component({
  selector: 'my-app',
  template: ' blblb'
})
export class AppComponent {

constructor(private _ngZone: NgZone){}

ngOnInit(){
    calledFromOutside() {
        this._ngZone.run(() => {
          this.runThisFunctionFromOutside();
    });
  }
  }
runThisFunctionFromOutside(){
   console.log("run");
}

How can i call the function runThisFunctionFromOutside which is inside App.component.ts

Angular Solutions


Solution 1 - Angular

I basically followed this answer, but I didn't want my "outside" code to know anything about NgZone. This is app.component.ts:

import {Component, NgZone, OnInit, OnDestroy} from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit, OnDestroy {
  constructor(private ngZone: NgZone) {}

  ngOnInit() {
    window.my = window.my || {};
    window.my.namespace = window.my.namespace || {};
    window.my.namespace.publicFunc = this.publicFunc.bind(this);
  }

  ngOnDestroy() {
    window.my.namespace.publicFunc = null;
  }

  publicFunc() {
    this.ngZone.run(() => this.privateFunc());
  }

  privateFunc() {
    // do private stuff
  }
}

I also had to add a definition for TypeScript to extend the window object. I put this in typings.d.ts:

interface Window { my: any; }

Calling the function from the console is now as simple as:

my.namespace.publicFunc()

Solution 2 - Angular

See also https://stackoverflow.com/questions/35276291/how-do-expose-angular-2-methods-publicly/35276652?noredirect=1#comment58266532_35276652

When the component is constucted make it assign itself to a global variable. Then you can reference it from there and call methods. Don't forget to use zone.run(() => { ... }) so Angular gets notified about required change detection runs.

 function callbackfunction(){   
   // window['angularComponentRef'] might not yet be set here though
   window['angularComponent'].zone.run(() => {
     runThisFunctionFromOutside(); 
   });
 }

constructor(private _ngZone: NgZone){
  window['angularComponentRef'] = {component: this, zone: _ngZone};
}

ngOnDestroy() {
  window.angularComponent = null;
}

Plunker example1

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');})

An alternative approach

would be to dispatch events outside Angular and listen to them in Angular like explained in https://stackoverflow.com/questions/36997625/angular-2-communication-of-typescript-functions-with-external-js-libraries/36997723#36997723

Plunker example2 (from the comments)

Solution 3 - Angular

Below is a solution.

function callbackfunction(){   
   window.angularComponent.runThisFunctionFromOutside();
}
       <script>
          System.config({
            transpiler: 'typescript', 
            typescriptOptions: { emitDecoratorMetadata: true }, 
            packages: {'js/app': {defaultExtension: 'ts'}} 
          });
          System.import('js/app/main')
                .then(null, console.error.bind(console));
        </script>

My App.component.ts

import {Component NgZone} from 'angular2/core';
import {GameButtonsComponent} from './buttons/game-buttons.component';
@Component({
    selector: 'my-app',
       template: ' blblb'
})
export class AppComponent {
        
  constructor(private _ngZone: NgZone){
  window.angularComponent = {runThisFunctionFromOutside: this.runThisFunctionFromOutside, zone: _ngZone};
}
        
        
    runThisFunctionFromOutside(){
      console.log("run");
    }
}

Solution 4 - Angular

An other approach without using global variables is to use pass a control object and bind its properties to the variables and methods to expose.

export class MyComponentToControlFromOutside implements OnChanges {
   
  @Input() // object to bind to internal methods
  control: {
	openDialog,
	closeDialog
  };

  ngOnChanges() {
	if (this.control) {
	  // bind control methods to internal methods
	  this.control.openDialog = this.internalOpenDialog.bind(this);
	  this.control.closeDialog = this.internalCloseDialog;
	}
  }

  internalOpenDialog(): Observable<boolean> {
	// ...
  }

  internalCloseDialog(result: boolean) {
	// ...
  }
}

export class MyHostComponent {
   controlObject= {};
}

<my-component-to-control [control]="controlObject"></my-component-to-control>

<a (click)="controlObject.open()">Call open method</a>

Solution 5 - Angular

I had a similar situation when using the callback 'eventClick' of the fullCalendar library, whose callbacks are returning from outside the angular zone, causing my application to have partial and unreliable effects. I was able to combine the zone approach and a closure reference to the component as seen below in order to raise an output event. Once I started executing the event inside of the zone.run() method the event and it's effects were once again predictable and picked up by angular change detection. Hope this helps someone.

constructor(public zone: NgZone) { // code removed for clarity
}

ngOnInit() {
    this.configureCalendar();
}

private configureCalendar() {
    // FullCalendar settings
    this.uiConfig = {
        calendar: { // code removed for clarity

        }
    };

    this.uiConfig.calendar.eventClick = this.onEventClick();
    
}

private onEventClick() {
    const vm = this;

    return function (event, element, view) {
        vm.zone.run(() => {
            vm.onSequenceSelected.emit(event.sequenceSource);                    
        });

        return false;
        
    };
}

Solution 6 - Angular

Just adding to @Dave Kennedy:

> Calling the function from the console is now as simple as: > > my.namespace.publicFunc()

1) If we try to access our component's public method from a different domain you will get caught into CORS issue (the cross origin problem, can be solved if both server and client code resides in same machine).

2) if you were to call this method from server using javascript, you will have to use window.opener.my.namespace.publicFunc() instead of window.my.namespace.publicFunc():

window.opener.my.namespace.publicFunc();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRoninioView Question on Stackoverflow
Solution 1 - AngularBig McLargeHugeView Answer on Stackoverflow
Solution 2 - AngularGünter ZöchbauerView Answer on Stackoverflow
Solution 3 - AngularRoninioView Answer on Stackoverflow
Solution 4 - AngularNico ToubView Answer on Stackoverflow
Solution 5 - AngularBrandon CulleyView Answer on Stackoverflow
Solution 6 - AngularKailasView Answer on Stackoverflow