Getting active scope with all app JavaScript objects


#1

I’ve got a somewhat advanced and somewhat edge case question.

I’m trying to create a generic approach for grabbing different class implementations from a static factory method. I’m using TypeScript and approach similar to what’s outlined on this blog. For quick reference, this is the technique that works on the web:

var instance = Object.create(window[name].prototype);
instance.constructor.apply(instance, args);
return <T> instance;

[Working Demo](http://www.typescriptlang.org/play/#src=interface%20ISecurityProvider%20{ %20%20%20%20name%3A%20string%3B %20%20%20%20getName()%3A%20string%3B }%20 class%20VeraSecurityProvider%20implements%20ISecurityProvider%20{ %20%20%20%20constructor(public%20name%3A%20string)%20{%20} %20%20%20%20public%20getName()%3A%20string%20{ %20%20%20%20%20%20%20%20return%20this.name%3B %20%20%20%20} } class%20Factory%20{ %20%20%20%20public%20static%20getDefault<T>(name%3A%20string)%3A%20T%20{ %20%20%20%20%20%20%20%20let%20instanceName%20%3D%20`Vera%24{name}`%3B %20%20%20%20%20%20%20%20let%20instance%20%3D%20Object.create(window[instanceName].prototype)%3B %20%20%20%20%20%20%20%20instance.constructor.apply(instance%2C%20["Johnny"])%3B %20%20%20%20%20%20%20%20return%20<T>instance%3B %20%20%20%20} } let%20test%3A%20ISecurityProvider%3B test%20%3D%20Factory.getDefault<ISecurityProvider>("SecurityProvider")%3B alert(test.getName())%3B)

Here’s the problem: “window” context/scope isn’t available in NativeScript (obviously)

FORTUNATELY, I “found” a runtime scope that does have all of my loaded app JavaScript objects called app_services_1. If you try to access this object at runtime, though, the name changes to app_services_2 (or whatever number you’re not using). :confused:

I’ve got a working solution that “cheats” and uses eval, but I’m hoping there is a better way to accomplish my goal WITHOUT using eval. Here’s what works now:

let as = eval("app_services_1"); // HACK
let instance = Object.create(as[instanceName].prototype);
instance.constructor.apply(instance, [controller]);
return <T>instance;

Anyone know any other way to either A) access this app_services_1 object, or B) get a reference to all loaded app JavaScript objects?

Like I said, edge case and advanced. :slight_smile:


#2

So…my hack is pretty fragile.

Seems app_services_1 (or whatever number is assigned) is not always available. Not sure what creates it, but definitely not a reliable execution variable.

So back to square one: How do you get (a reliable) reference to a scope that has all app created JavaScript objects?


#3

@toddanglin - Because I’m not following your use case real well I’m not sure if this will help.

Have you tried the global scope. global is where all global objects live like the window scope in browsers.

In fact the statement (lets just take console.log) is actually global.console.log ; so you can call global.console.log("hi"); or console.log("hi"); and they call the same function.

Second thing I assume you are using NG2 (Angular2) (based on app_services_x reference in your post); NG2 is a lot different in the way it scopes things than PAN (Plain Awesome NativeScript) so depending on what you are trying to do; NG2 or PAN, it will be different; however “global” is available in both environments.

Nathanael A.


#4

Sorry - I know this is an obscure use case.

My primary goal: create a generic factory class in TypeScript that can use a string to return references to runtime class objects. So rather than having a bunch of separate factory classes (like "CustomerFactory" and "OrderFactory"), I could just have "ServiceFactory<T>". Then I could do something like this:

let service = ServiceFactory<ICustomer>("Customer");

(The string is necessary because the <ICustomer> bit is just TypeScript and obviously not available at runtime.)

In my app, imagine I have multiple implementations of objects to support different backend systems, and I don’t know until runtime which I’m going to use. So I might have:

interface ICartProvider
abstract class BaseCartProvider implements ICartProvider
class PayPalCartProvider extends BaseCartProvider
class CustomCartProvider extends BaseCartProvider

In my hypothetical example, at runtime based on user settings, I want to do this:

let cartProvider: ICartProvider;
cartProvider = GenericFactory<ICartProvider>("CartProvider");

And then in the GenericFactory<T> do something like this:

let userSetting = "PayPal";
let targetProvider = userSetting + "CartProvider"; // so target is now "PayPalCartProvider"

The BIG question is this: How do I create/return an actual instance of PayPalCartProvider at runtime from this string?

The blog post I linked to above shows a way to do this in the browser using the window context. It works because the global window context has an instance of all TypeScript classes in your app. I’m trying to figure-out how to adapt that solution to work in NativeScript. Unfortunately, the raw global scope in {N} does not include references to all of your app’s TypeScript classes. Only place I could find that is in the hard-to-rely-on app_services_X variable.

Hope that helps clarify (a bit). This is “PAN” for what it’s worth (no Angular – just TypeScript).


#5

Well this should be fairly simple; seems like you are over complicating it :grinning: – why not do this:

Lets pretend you have three cart providers, Paypal, Generic, and Awesome.
So you should have the following files: PaypalCartProvider.ts, GenericCartProvider.ts, AwesomeCartProvider.ts and finally the base “CartProvider.ts” each of them use the CartProvider interface; so they are all compatible to CartProvider.ts

Code:

// Provide dynamically at run-time
let userSetting = "Paypal"; 

// Build our Target Provider
let targetProvider = userSetting + "CartProvider");

// Load in the target Provider; if you need to provide a path to it prepend the path.
const Provider = require("services/providers/"+targetProvider);  

// Create the new provider class, since all have the CartProvider interface the  
// rest of the code is the Same For all Cart providers classes
let newProvider = new Provider[targetProvider](standard, init, parameters, ...);

// Actually run the code in the CartProvider
newProvoider.someCommonProviderFunctions();

Inside PaypalCartProvider.ts

export class PaypalCartProvider extends CartProvider …
// All the new Paypal specific code.

Inside AwesomeCartProvider.ts

export class AwesomeCartProvider extends CartProvider …
// All the Awesome Cart Provider code

All this code can be wrapped up in a simple function, let myProvider = createCartProvider("Paypal"); and it would return you the Paypal cart provider. You can easily extend the cart providers; just inherit from the CartProvider and you can create as many providers as you need.

P.S. What generates the app_services_x variable; I don’t recall seeing that in any of my PAN code, JS or TS based.


#6

Interesting idea! I had not considered using the require() syntax to be my “dynamic loader”. I think that might work. I"ll give it a try and report back.

As for the "app_services_X" object, I don’t know much about it. I “found” it while debugging in the execution scope chain. It’s not part of global, but not sure what triggers the creations of this scope. It was the same scope where more common variables like appSettings could be found. I think we’ll need the core team to weigh-in on this undocumented object…or we’ll need to spelunk the source to see what’s going-on.