简体   繁体   中英

Angular2 - how to call component function from outside the app

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

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()

See also How do expose angular 2 methods publicly?

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 Angular 2 - communication of typescript functions with external js libraries

Plunker example2 (from the comments)

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

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>

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;

    };
}

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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM