How to expose angular 2 methods publicly? - javascript

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

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

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.

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.

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

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

Related

calling angular component function from plain javascript

I am using angular for my web app and i need bluetooth on one of my pages.
I am using loginov-rocks/bluetooth-terminal(https://github.com/loginov-rocks/bluetooth-terminal) for the bluetooth connection and it works i can conect my device and see data from it. now the problem i have is i cannot get the data from my recieve function to my angular component, i can print data to console, but that is not what i want, i want to parse my data and set some variables in my angular component from it.Here is my code:
let bluetooth = new BluetoothTerminal();
bluetooth.receive = function (data) {
console.log(data);
//i want to call ParseBtData here from my angular component to parse data
//or somehow send data and cach it for parsing inside my component
};
export class GpsAppComponent extends AppComponentBase implements OnInit
{
//all the angular stuff
parseBtData(data) {
//parse my BT data and set some variables inside my component...
};
}
I have tryed making BluetoothTerminal inside the component, but i still cannot call any function to parse my data. Is it even possible to do that, or is there another way i should aproach my problem?
In general if you create an object javascript you can do it using declare, then the only is override the "received" data. But a change because an event not controlle by Angular, you need say to Angular that "something" has changed outside Angular, so you need use ngZone, some like:
//DISCLAMER: I don't know if work
declare var bluetooth = new BluetoothTerminal();
export class AppComponent implements AfterViewInit
{
constructor(private ngZone:NgZone,private dataService:DataService){}
ngAfterViewInit(){
bluetooth.receive = (data)=> {
this.ngZone.run(()=>{
this.dataService.sendData(data)
})
};
}
}
Then you only need define in your service
bluethoodData:Subject<any>=new Subject<any>()
sendData(data:any)
{
this.bluethoodData.next(data)
}
And you can subscribe in any component to dataService.bluethoodData
this.dataService.bluethoodData.subscribe(res=>{
console.log(res)
})
I managed to solve my problem by moving my bluetooth inside the component and doing:
this.bluetooth.receive = (data) => {
//console.log(data);
this.writeToTerminalConsole(data);
};

BehaviorSubject not working Angular 5

I cant seem to figure out what the issue is,
I have a service that has a behavior subject like so...
popupSource = new BehaviorSubject<any>('');
popup(component) {
this.popupSource.next(component);
}
then In my header component
popupClick() {
this._service.popup('example');
}
then in my header.html
<button (click)="popupClick()"></button>
then In my app component
ngOnInit() {
this._service.popupSource.subscribe((result) => {
console.log(result);
})
}
so whats happening is the click is firing the this._service.popup('example'); but its never hitting the subscription...
I've put breakpoints on each function and It succesfully reaches this.popupSource.next(component) but then nothing?? Every Time I click the button I should be getting the console log.
Im not sure what Im doing wrong... Ive left out code for brevity sake so please let me know if you need more information
EDIT
Ive also tried doing
private popupSource = new BehaviorSubject<any>('');
getPopup = this.popupSource.asObservable();
popup(component) {
this.popupSource.next(component);
}
and the in my app component listend to the getPopup instead
ngOnInit() {
this._service.getPopup.subscribe((result) => {
console.log(result);
})
}
and that's not working either, I cant figure out what the problem is...
Thanks
Your issue here is that you provide your service in two different modules, that end up with two instances of your service. The simplest way if you are using Angular v6 is to use the providedIn flag in your service:
import { Injectable } from '#angular/core';
#Injectable({providedIn: 'root'})
class myService {}
With this way you don't need to provide your service in the providers array of any module, it will be automatically provided in the root injector.
Documentation about this can be found here : Angular providers.
If your are using Angular v5 you should only provide your service in your AppModule.
In your service, write a new method like this:
popupSource = new BehaviorSubject<any>('');
getPopupSource() {
return this.popupSource.asObservable();
}
And subscribe to that instead of subscribing directly to the subject itself. You could just add the 'asObservable()' part whenever you subscribe to it instead of creating an entirely new method, but I like the separate method so I can keep the Subject private, and I usually subscribe to something multiple times in different places throughout an app, so it cuts down on repetition.
In your component :
this._service.getPopupSource().subscribe( result => { etc...})
EDIT :
Demo recreation of your scenario - https://stackblitz.com/edit/angular-n6esd5
You may not have the same instance of service, my same problem was that I had #Injectable({ providedIn: 'root'}) for my service, but also I put this service in the component providers [] array, just remove the providers array, then it works

Angular/TypeScript: How to get a reference to a class from a string at runtime without importing

I'm trying to figure out how to get a reference to a class in Angular 2+ (Angular 5) from a string at runtime. I tried the examples on this page. This one didn't work for me:
console.log((<any>Object).create(window[className])); // undefined
And the others are using an import, which I'm trying to avoid.
I'm not using a namespace, but don't know if Angular has one of its own. I tried snooping on the window object to see if I could find anything. All I found were getAllAngularRootElements, getAllAngularTestabilities, and getAngularTestability, but those didn't seem like what I was looking for.
I had a similar need once for dynamically rendering components and only having a string reference to the class that needed to be injected into the page (dynamic dashboard type of app). I ended up doing the following:
Create service to hold onto reference of component by string name
Inject service into module component was part of and register the component
Inject the service into the component that needed to get the component by string name
This was roughly what I had for the service (the class took care of actually creating the dynamic component instead of getting the reference like below):
export class DynamicComponentService {
private dynamicComponentTypes: { [type: string]: Type<BaseInterfaceSectionComponent> } = {};
registerDynamicComponentTypes(...dynamicComponentTypesToRegister: { component: Type<BaseInterfaceSectionComponent>, name: string }[]) {
dynamicComponentTypesToRegister.forEach(dynamicComponentType => {
this.dynamicComponentTypes[dynamicComponentType.name] = dynamicComponentType.component;
});
}
getDynamicComponentType(name: string): Type<BaseInterfaceSectionComponent> {
return this.dynamicComponentTypes[name];
}
}
I was unaware until doing this, but you can actually inject dependencies into a module's constructor. I used this feature to use the service to register the dynamic components:
export class BarChartContentModule {
constructor(dynamicComponentService: DynamicComponentService) {
const dynamicComponent = { component: BarChartContentComponent, name: 'BarChartContentComponent' };
dynamicComponentService.registerDynamicComponentTypes(dynamicComponent);
}
}
Not sure if this is what you were looking for, but figured I'd share.

Write global functions to use in all components in angular

Note : Its not for global variable but for a global common function to perform a functionality on all components
I am working on an angular app where I have around 400 components in different modules, almost all components have one same kind of functionality as mentioned below
There is a sections on many pages which shows a "How to work section" which can be closed by users and will remain closed unless they open it again, I have done it with cookies which I set on click on close or open icon but this function is written in a component and this needs to be imported in other components
I want to create a functions somewhere which perform this functionality on click on icon and can be called without importing any component in others.
One way to do it ( as I thought ) could be create a JavaScript function in a file and load it in index file and then call this function on click on close and open icon
Not sure if this is the best way to do this. Hope someone will come up with a better solution.
1. create your global function service, i.e. 'funcs.services.ts' under 'services' directory:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class FuncsService {
constructor() { }
myGlobalAddFunction(a){
return a++;
}
mySecondFunc(){
// add more... and so on
}
}
2. Import the function in your component:
// your path may different
import { FuncsService } from './../services/funcs/funcs.service';
//...
constructor(
private funcs: FuncsService
) {}
ngOnInit(): void {
let x = 1;
myResult = this.funcs.myGlobalAddFunction(x);
// Then you are expecting 2 for return value
}
3. Hope that works... :)
You can export a function that is a written in .ts file and then call it in all your components.
export function myFunction(){
// Do something
}
And then import the function myFunction() in other components. That works fine for me and can be useful in certain cases
This isn't the best solution (in my opinion). The best solution would be to either create a service, or an utils class.
But if you want to do this, I suggest you make a JS file, that you declare in your angular-cli.json file under the scripts property, containing your functions.
EDIT Now that you've came back to reason, here is a code sample to make utils classes.
export const IMG_UTILS = {
convertPngToJpg = (picture: any) => {
// Your logic here
}
};
export const VIEW_MANAGER = {
isAdblockActive = () => {
// test if an ad-blocker is active
}
};
You can make any utils class you want in a const, then put it into a file. Then, you can put this file in an utils folder, and request it with
import { VIEW_MANAGER } from 'src/app/utils/view-manager';
Otherwise, you can make a service, which is handled by Angular, with a console command such as
ng g s services/view-manager/view-manager
And it will behave the exact same way : you will import it in your components to use it.
Hope this helps !
The most recommended way is to use a service and inject it whenever needed, but there is a way to have a function available globally.
Although I don't think it's a really good idea, you can add the function in the index.html file, then whenever you want to use it, you have to use #ts-ignore to avoid an error from being thrown.
e.g
index.html
<script>
function globalFunc(){
alert(2)
}
</script>
anywhere else on the app
// #ts-ignore
globalFunc();
List item
Just to chime in with possibly a duplicate answer albeit more fleshed out... I have a utilities class which I use.
For example:
export class Utilities {
// Simple promise wrapper for setTimeout. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#creating_a_promise_around_an_old_callback_api
public static Wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
}
The class is referenced in a component via the import statement:
import { Utilities } from 'path/to/Utilities';
And then you can call your static methods thus:
Utilities.Wait(30000)
.then(() => DoStuff())
.catch(() => console.log('Darn!'));
I would tend to use RxJs but I've written it this way to keep things a little cleaner.

Angular 2 issue in accessing componet property

i am new in Angular 2 and trying to develop a project with basic CRUD functionality.
I am facing an issue that i have a User component and trying to access it's property in ngAfterViewInit hook, but it's showing undefined.
Here is code of User component.
export class UserComponent implements OnInit, AfterViewInit
{
private users: User[];
private userRoles;
ngOnInit(){
//get users from service
this.getUserDetails();
}
ngAfterViewInit() {
console.log(this);
console.log(this.users);
}
getUserDetails() {
this._userService.getUsers().subscribe(users => this.users = users);
}
}
Below is screen shots of console. I can see that Object has property users but it shows undefined in console.
Please give suggestion if i missed anything.
Thanks in advance.
Check user property inside getUserDetails. You can use users property in your template. Check component lifecycle hooks to get better idea about component lifecycle.
getUserDetails() {
this._userService.getUsers().subscribe((users) => {
this.users = users;
console.log(this.users);
});
}
I think this is not angular 2 issue...
This is typescript issue accessing this instance globally .
We can do this by using let and declaring variable in typescripts...
When javascript object assigned is cloned to other object then changes made to one is automatically passed to other
So this issue also arrises when we want to access typescript this object inside jquery functions
let _this = this;
something.each(function() {
console.log(_this); // the lexically scoped value
console.log(this); // the library passed value
});

Categories

Resources