3

I have an angular-cli generated project that I would like to use with a deployd backend. Deployd provides a script for accessing it's API's that can be loaded from http://<deployd-host>/dpd.js. This creates a global dpd object that can access the API from javascript global context (e.g. from Chrome dev tools console).

I would like to wrap this in an Angular2 service so I can inject a mock one for testing etc. The tasks are to load the script from the URL and then gain access to the global dpd object. I've looked at this SO post but haven't been able to get the accepted answer to work. If I add the script manually to the document object, I can't access the dpd object on window.

Also, the URL that the script is loaded from will be different based on environments, e.g. http://localhost:3000/dpd.js for local dev, http://dev.example.com/dpd.js for staging and http://www.example.com/dpd.js for production. So ideally I would be able to configure that in the service as well.

Looking for something like below to work.

@Injectable()
export class DpdService {
  constructor() {
    if (getEnvironmentSomeHow() == 'development') {
      loadScriptFrom("http://localhost:3000/dpd.js");
    } else {
      loadScriptFrom("http://dev.example.com/dpd.js");
    }
    dpd = window.dpd;
  }

  public session(): Observable<Session> {
    return Observable.fromPromise(dpd.sessions.get());
  }
}
Community
  • 1
  • 1
Saad Farooq
  • 13,172
  • 10
  • 68
  • 94
  • can you able to share some code – Pankaj Badukale Jan 07 '17 at 07:08
  • Possible duplicate of: http://stackoverflow.com/questions/39942118/how-to-inject-different-service-based-on-certain-build-environment-in-angular2/39942256#39942256 Very detailed explanation with code – Karl Jan 07 '17 at 11:12
  • And what is the problem that you have with the approach you've shown in the question? – Estus Flask Jan 07 '17 at 23:59
  • @estus 1. Don't know how to the environment, 2. Don't have an elegant implementation for `loadScriptFrom`, 3. If loading by adding a script tag to the document, the dpd object is not available on the window when I use it. If I check from console in Chrome, it's available, so that seems like an async issue. – Saad Farooq Jan 08 '17 at 00:42

1 Answers1

1

Application environment totally depends on the choice of the developer. These can be conditionally included TS files that depend on Node environment variable. It can be a single file that defines Angular providers depending on client-side global variable (likely provided with Webpack DefinePlugin or EnvironmentPlugin, see angular2-webpack-starter for example). In its most simple form it is just client-side global ENV variable, all decisions are made in-place - it can even be set in HTML with server-side templating:

<script>
   window.ENV = <% SERVER_SIDE_ENV_VARIABLE %>
</script>

Due to the fact that the script should be loaded on app initialization, it has to be loaded in APP_INITIALIZER multiprovider:

...
import {APP_INITIALIZER} from '@angular/core'
import {DOCUMENT} from '@angular/platform-browser'

@Injectable()
export class DpdService {
  dpd: any;

  constructor(@Inject(DOCUMENT) document: Document) {}

  load() {
    const srcBase = window.ENV === 'dev'
      ? 'http://localhost:3000/'
      : 'http://dev.example.com/';

    const script = this.document.createElement('script');
    this.document.body.appendChild(script);

    return new Promise((resolve, reject) => {
      script.onload = resolve;
      script.onerror = reject;
      script.async = true;
      script.src = srcBase + 'dpd.js';
    }).then(() => {
      this.dpd = window.dpd;
    });
  }

  session(): Observable<Session> {
    return Observable.fromPromise(this.dpd.sessions.get());
  }
}

export function dpdAppInitializerFactory(dpdService: DpdService) {
  return () => dpdService.load();
}

...
providers: [
  DpdService,
  {
    provide: APP_INITIALIZER,
    useFactory: dpdAppInitializerFactory,
    deps: [DpdService],
    multi: true
  }
],

...

At the point when the app is initialized, the promise from load method is fulfilled and dpd property is set.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • That works great. A couple of things... `DpdService.load()` should be `dpdService.load()`. Also, after doing this, I get `Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 23:19 in the original .ts file), resolving symbol AppModule` – Saad Farooq Jan 08 '17 at 05:46
  • As for the environment, this is using `angular-cli` which should manage the environment. – Saad Farooq Jan 08 '17 at 05:47
  • So the error seems to be an issue with with `aot`... should have nothing to do with your solution. – Saad Farooq Jan 08 '17 at 06:06
  • To avoid the problems with AoT, it is likely should be `export const dpdAppInitializerFactory = (dpdService: DpdService) => () => dpdService.load()` and `... useFactory: dpdAppInitializerFactory`. – Estus Flask Jan 08 '17 at 12:30
  • That doesn't work... but making it a function does. Could you update your answer... `export function dpdAppInitializerFactory(dpdService: DpdService) { return () => dpdService.load(); }` – Saad Farooq Jan 08 '17 at 16:27
  • Sure. Glad it worked for you. I don't do AoT myself, but it is usually solved like that, to my knowledge. – Estus Flask Jan 08 '17 at 18:53