1

I'm trying to call a function defined within an angular component.ts from another javascript library

The library supports a snapin, where I can define HTML Elements, which will be rendered (they are not part of angular).

Here is my html (not in a component's template)

<button onclick='ping()'>PING Button</button>
<button onclick='pong()'>PONG Button</button>

How can I, from the html above, call the pong component method defined my component.ts

import { Component, OnInit, AfterViewInit, Inject, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';

function ping() {
  alert('PING PING');
}


@Component({ ...
})

export class Component implements OnInit, AfterViewInit { ...
 pong() { //This is the method I want to call from outside of the component's template
    alert('pong pong');
  }
}

I tried, but it will not work

<button (click)="pong()">PONG Button</button>

But I have no idea to call the "pong()" function normally

Thanks!

David
  • 33,444
  • 11
  • 80
  • 118
WMY
  • 13
  • 3

2 Answers2

2

If you really need this, you can make the method available in the window object

component.ts

constructor()
{
    window['pong'] = () => this.pong();
}

pong()
{
    alert ('pong inside component')
}

Then in your html, you can use old style event handler

<button onclick="pong()">Pong</button>

Here is a stackblitz demo

Note: If you have several instance of the same angular component implementing this solution, you'll only have once instance of the method. You could save them all to an array if needed, but you'll need to know which one to call

David
  • 33,444
  • 11
  • 80
  • 118
1

I guess you want to use a non-angular library from angular, which has a global callback. Be warned that this can lead to problems, because you have to manage the lifecycle of the non-angular thing from angular.

From the angular template, you can only call methods on the component class, and you can't call a global callback. You can however create a method on the component class, and call the global callback from there.

There's one more thing before that's possible: typescript doesn't know about your global callback, so you have to explictily declare it, see the example. This tells typescript that there's something that is created outside of typescript, so it will let you call it.

import { Component } from '@angular/core';

declare const libMethod: (any) => any;

@Component({
  selector: 'my-app',
  template: `
    <button (click)="myMethod($event)"></button>
  `,
  styleUrls: []
})
export class AppComponent  {

  public myMethod(param) {
    libMethod(param);
  }
}

If you plan to use that library from multiple of your angular components, then you might want to create a service, declare the global callback only in that, and create a method on the service. That way, this somewhat hacky declaration will not be littered all over your code, but contained to a single place. It also makes your life easier, if you upgrade/replace this library.


Answers to questions in the comments:

TBH I don't completely understand the situation. (Who calls the backend, when it returns the HTML? You from Angular, or the lib? Does the lib process the HTML? Or what does it do?)

Some suggestions: create a global singleton service, which puts up one of its methods to the window (dont' forget to bind it if you use this inside the method) to serve as a callback for the lib. When the lib calls it with the data, regardless of who/when actually triggered the lib to do its thing, the service stores the data in a subject, and the service also provides an observable of the data (maybe with a shareReplay(1) so that the consumers always get something).

With that, actually displaying the data is fairly easy, you can just use the async pipe, and not care about how/when the data got there in the first place, and don't have to sync the component's lifecycle with the service.

Also, you probably need to use https://angular.io/api/platform-browser/DomSanitizer#bypasssecuritytrusthtml but I am not sure about that, since I never had to inject HTML. Speaking about which...

Important security notice: if you inject HTML from outside of angular, and that is hijacked, you just opened up your page to all kind of nasty cross site scripting things.

Alex Biro
  • 1,069
  • 1
  • 14
  • 27
  • Ok, I understand! but is it possible to define a function within component.ts and make it accessable on global scope - outside of angular, like an export? – WMY May 13 '20 at 14:56
  • Yes, it's basically the same thing, but you don't call a global method, but you assign your local method to the window object. But make sure that when the component is destroyed (eg. in `onDestroy`), you remove the reference from the window, otherwise you just built a memory leak, and also, it doesn't make sense to call your component's class, once the compoent is not attached to the DOM any more. btw can you maybe describe the situation in a bit more detail? Why do you want to pass your component to an external lib? – Alex Biro May 13 '20 at 15:03
  • Accessing the window the angular way: https://juristr.com/blog/2016/09/ng2-get-window-ref/ – Alex Biro May 13 '20 at 15:03
  • I have to use a external library (docuvieware). There is JavaScript library (frontend) and a C# library (backend). Now I'm using Angular as frontend for my app and .net core as backend. This library supports a snap-in, which must be configured within the backend to call my own javascript/ts functions. Within the backend HTML will be rendered and returned to the frondend - response of a REST request. Within Angular I replace a child element to put the new HTML content. So I defined a html button with "onclick"='pong()'. This will be included within the toolbox, rendered by the library. – WMY May 14 '20 at 06:25
  • Answered in my original reply. – Alex Biro May 14 '20 at 08:40