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());
  }
}
4
  • can you able to share some code Commented Jan 7, 2017 at 7:08
  • Possible duplicate of: stackoverflow.com/questions/39942118/… Very detailed explanation with code Commented Jan 7, 2017 at 11:12
  • And what is the problem that you have with the approach you've shown in the question? Commented Jan 7, 2017 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. Commented Jan 8, 2017 at 0:42

1 Answer 1

1
+100

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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
As for the environment, this is using angular-cli which should manage the environment.
So the error seems to be an issue with with aot... should have nothing to do with your solution.
To avoid the problems with AoT, it is likely should be export const dpdAppInitializerFactory = (dpdService: DpdService) => () => dpdService.load() and ... useFactory: dpdAppInitializerFactory.
That doesn't work... but making it a function does. Could you update your answer... export function dpdAppInitializerFactory(dpdService: DpdService) { return () => dpdService.load(); }
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.