Plain Javascript as Angular 2 service - javascript

I need to add a hosted third-party JavaScript file in an Angular 2 component. This file is updated anytime a change is made in the associated third-party vendors proprietary system, so I cannot simply pull down a copy, include it locally, and import it into the project.
Typically I would include this file at a top level in a script tag, and then simply use declare <windowvar>: any to get access to it. However in this case, since the component itself is trying to load the script, I cannot declare the window variable because it does not exist on the window object at the time the component is loaded, which generates an error.
I can load the script by manually adding a script tag, and that works, however I need access to the window variable it creates in order to use it properly. And I cannot simply use an interval to look for it because typescript throws a fit that <windowvariable> does not exist on object window.
Is there any way I can 1) Load the hosted JavaScript file inside the component, and 2) Get access to the window variable created by the loaded JavaScript file?

Update 1: Based on the comments, the previous solution will be not help you.
You can do that by using OpaqueToken in Angular2
1. Create a Token that is used to find an instance as below in a separate ts file.
import { OpaqueToken } from '#angular/core'
export let name_of_The_Token = new OpaqueToken('name_Of_The_Window_Object');
2. In your App.module, you need to import and declare a variable that is the name of your window object which makes the Token as a angular2 service so that you can use properties, methods in that javascript file across your components.
import { name_of_The_Token } from '/* file_Path */';
declare let name_Of_The_Window_Object : any; //below your import statements
Step 3: Inject it to providers array of your module.
{ provide : name_of_The_Token , useValue : name_Of_The_Window_Object }
Guidance to use this token in components
Import the token just like any other service and #Inject from angular-core
import { name_of_The_Token } from '/* file_Path */';
import { Inject } from '#angular/core';
In constructor of the component
constructor(#Inject( name_of_The_Token ) private _serviceObject : any )
Any where in your component you can use the variables and methods of your javascript file as
this._serviceObject.method1()
this._serviceObject.variable1
.....
Note: One drawback is that you will not get intellisense.
Overcoming it:
If you are looking for intellisense you need to wrap the methods and variables inside an interface and use it in the type**(instead of any)** of your token as
export interface myCustom {
method1(args): return_Type;
method2(args): void;
.....
}
LIVE DEMO of ToasterService

Effectively, you're trying to pick up a "global module" javascript library from a CDN. (I assume the 3rd-party lib is not in CommonJS, AMD, UMD, or other module format, since it is accessed through a sole global variable.)
So the first question is where is the corresponding .d.ts file? It contains the names and interfaces that inform Typescript of the 'shape' of the library, as well as declaring that global variable will exist. If your 3rd-party doesn't provide it you'll need to write it yourself. It will contain not just the declaration of the global var, like
declare var theGlobalVarInQuestion: IInterfaceOfStuffInsideLibrary;
but also the declaration of said Interface, and its properties and their types, all the way down. Like this: https://github.com/catamphetamine/libphonenumber-js/blob/master/index.d.ts
You can include the .d.ts file in /node_modules/#types/nameOfSaidLibrary but you'd need to check it into your source repo (with possible .gitignore gymnastics) especially because a npm prune will remove it. Or if you put it elsewhere, modify the tsconfig.json typeroots property to look in both that place in addition to its usual /node_modules/#types/ folder.
Just to be clear, the .d.ts file doesn't (and shouldn't) actually create the variable; it just states that it will be there so the Typescript compiler won't complain. Whether or not it is there at runtime is decided by however you're loading the js.
If you're not loading it via script tag in the index.html, then either a Typescript import statement in the consuming component can do so (given the right config of SystemJS or whatever you're using) or the consuming component can dynamically create and append a new script tag to the index.html. Just make sure your module loader isn't trying to download and immediately bundle it with the rest of your app at buildtime. It sounds like you want the lib to be downloaded anew each time at runtime.

Related

How to expose a class to the global scope with esbuild?

Update
user #TKoL suggested defining a property in the window object. This produces the result I wanted to achieve, although I do not know if it is the correct way to proceed. I will be using this method for the time being. Thanks!
I am using esbuild in my project (first time using a bundler / developing JS in this "style").
The project is a small web component for which I developed a class that the end user should be able to utilize in their scripts.
// this is the component, which is then bundled and minified by esbuild.
// i have omitted various imports required in the class here.
export default class Widget extends HTMLElement {
/// code here is not relevant
}
customElements.define('custom-widget', Widget);
As it stands, the end user is able to utilize the widget from HTML like this:
<custom-widget some-custom-attribute="some-value"></custom-widget>
let widget = document.querySelector('custom-widget');
widget.someMethodDefinedInTheClass();
However, I would like to allow the user to do it all via JavaScript as well.
How can I make esbuild expose the Widget class to the global scope? I'd like to do this to enable behaviours such as:
let wOptions = {}; // object of initialization options for the widget
let widget = new Widget(wOptions);
someContainer.append(widget);
widget.someMethodDefinedInTheClass();
To make any way bundled js file to expose anything to the global scope you have to set it as a property of global object (that represents the global scope) like
globalThis.Widget = Widget;
globalThis keyword is a best way to use global object in javascript because it works the same on browser page, nodejs or service worker environment. If your code will be only executed on browser page you can also use window keyword.

How do you export an interface into global namespace - angular

I have a file with defined interfaces I'd like to share with my app's global namespace. My first attempt to do this was that I imported the interfaces.ts file into my app.module.ts and placed the interface into the exports array, but types/interfaces cannot be used in this way, so I thought of a few questions related to this small issue.
Does app.module.ts need to be involved for exporting to the global namespaces?
Is there a different way to do it (probably a native javascript way)?
If app.module.ts is required to do this, how do I import an interface in a way that my app.module.ts shares it with the rest of my app, so that I wont have to constantly import it in my components?
Checkout the global namespace here: https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html
/*~ Note: If your global-modifying module is callable or constructable, you'll
*~ need to combine the patterns here with those in the module-class or module-function
*~ template files
*/
declare global {
/*~ Here, declare things that go in the global namespace, or augment
*~ existing declarations in the global namespace
*/
interface String {
fancyFormat(opts: StringFormatOptions): string;
}
}
Beware though:
This pattern is somewhat dangerous due to the possibility of runtime conflicts, but we can still write a declaration file for it.
It's better to have a SharedModules for everting that you want to use in the entire application more than one place according to the Angular official document. In this SharedModules you can have different directory app structure for all common things such as Models, Services, Interceptors, Pipe, components and etc.
Then Use this module on your main app.module.ts and import what you need.
For more information study this helpful & quick guide on PluralSight.

A clean way to allow React shared component to get configuraiton property without having to pass it all the way down

I have about 8 simple UI's for different applications, all of them make use of a shared components library I also produce. I recently added a feature to modify how querying is done in the low levels of a some utility functions used in all the saga of all my UIs.
I want each of my UIs to be able to include an optional configuration value to tweak this new querying logic in their configuration files. However, the method that needs the property is very low level, I'd prefer not to have to add an extra property to 5-6 methods to pass one value down to the method that needs it.
These shared components are used only for my UI, which all have a standard format. As such I don't need them to be fully generic, I have no complaint with hard coding their checking the standard location of my configuration file in my UIs. However, since the shared components are a separate NPM package I don't know how to reference the location of a configuration file in the applications calling the UI.
Is there any clean way to get the value from the configuration file other then just passing it through all the relevant methods?
It's simple. You can create configuration file where you'll export your logic. And when you need that config, import that and use. Here's an example:
configuration.js
export const test = (myvar) => {
return 'test: ' + myvar
}
component.js
import { test } from 'configuration.js'
test('pass') // test: passed

javascript one class per file using eslint

I would like to use javascript classes with one class per file. It is part of a larger project using eslint. I started with:
/*global CSReport*/
/*global CSManager*/
class CSMain {
constructor() {
this.report = new CSReport();
this.manager = new CSManager(this.report);
}
launchReport(...
}
However, eslint generates an error saying CSMain is defined but never used. This led to the idea of using export and import which seemed better than making everything global (side note: CS in front of main is the old style method to avoid global conflicts)
The question is how to put this together. The release version will be a single (uglified) file, so the class file names will no longer exist when they are all concatenated together in (say) csCompiled.js.
Questions:
Import uses a file name. Should I use the CSCompiled.js name rather than the file names before concatenation?
Do I want a single module or a module for each class?
Do I need to export every class and import every class it uses?
I am not fully sure how angular accesses this code but am thinking to import csMain.
I tried to find an answer to this but am only finding older posts that don't use ecmascript 6 and classes. If an answer to this exists, I am not sure how to get to it.
Background:
The main project uses angular 1. This code is separate for legacy reasons. It is currently written in java using gwt, but we want to move to javascript to remove the reliance on gwt. It is about 30-40 files (classes) total to convert.
The code gets and handles data from the server for report requests. There is a lot of pre-processing done before it is handed back to the rest of the UI.
I have used javascript for an established project using angular, but lack expertise on how to create new projects.
I am trying to use basic javascript for this, so it won't need updating if (for example) we go from angular 1 to the current versions. I do not yet know if this is a good way to do it.
ESLint is complaining because you are not exporting the class you created, therefore, it can't be accessed by other modules. You can fix that with a simple line at the end
export default CSMain;
Import uses a file name. Should I use the CSCompiled.js name rather
than the file names before concatenation?
Use the file name before you compile/transpile/uglify/etc. After that it will all become 1 file and the bundler will take care of that for you.
Do I want a single module or a module for each class?
Completely optional, I like to have 1 class per file and then 1 file for the module (index.js) that lists all classes in that module.
Do I need to export every class and import every class it uses?
Yes, you need to import everything your module will use and export everything that should be public or "importable" for other modules.
I am not fully sure how angular accesses this code but am thinking to import csMain.
It all depends on how you export your file. Make sure to import the same name your module/file is exporting.

Why are package imports needed in Meteor

About a year ago I have used Meteor, and now I want to use it again, but many things have changed.
When I follow the Blaze tutorial on Meteor.com, they add imports on top of their files:
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
I got the app working. But when I comment the imports out, the app keeps working like it should work. Why are these imports needed?
I am still using the regular Javascript, not ES6.
Thanks!
The import statement is used to import functions, objects or primitives that have been exported from an external module, another script, etc.
The name parameter is the name of the object that will receive the exported members. The member parameters specify individual members, while the name parameter imports all of them. name may also be a function if the module exports a single default parameter rather than a series of members. Below are examples to clarify the syntax.
Import an entire module's contents. This inserts myModule into the current scope, containing all the exported bindings from "my-module.js".
For more detail about the different ways we can use import along with their usage, please check this.
They still use the old globals for backwards compatibility. However it is recommended to use the imports so if in some future release they remove the globals your code will still work. You can read more in the appropriate section of the guide.
Ok you know import is to import an exported object from another file already.
The point that you may have missed is that MDG heard the need to stop loading everything by default, or at least to provide a mean to control what is loaded in memory and what is not.
Look for the /imports special directory.
Files in that folder are no longer loaded automatically, but only through import statement.
As for the tutorial, I guess they did not explained this functionality, and because it imports only standard functionalities which are still loaded eagerly for backward compatibility, it does not change anything removing those statements.

Categories

Resources