Plain Javascript as Angular 2 service

2020-01-29 01:50发布

问题:

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?

回答1:

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

  1. 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';
    
  2. In constructor of the component

     constructor(@Inject( name_of_The_Token ) private _serviceObject : any )       
    
  3. 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



回答2:

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.