Search code examples
javascriptangulartypescriptangular2-aotangular-compiler-cli

window is undefined when used as useValue provider with Angular 4 AoT


When Angular 4.0.2 application is compiled ahead-of-time, and the provider is defined as useValue

import { OpaqueToken, Provider } from '@angular/core';

export const windowToken = new OpaqueToken('window');
export const windowProvider = { provide: windowToken, useValue: window };

and used like

@NgModule({ providers: [windowProvider], ... })
export class AppModule {}

it compiles ok but results in window being undefined when injected as

constructor(@Inject(windowToken) window) {
   window.navigator...
}

The error is thrown on bootstrapping:

TypeError: Cannot read property 'navigator' of undefined

On a closer look at auto-generated app.module.ngfactory.js it appears that it is indeed undefined:

...
import * as import39 from './window';
var AppModuleInjector = (function (_super) {
    ...
    AppModuleInjector.prototype.createInternal = function () {
        ...
        this._windowToken_26 = undefined;
        this._SomeService_27 = new import16.SomeService(this._windowToken_26);
    }
    AppModuleInjector.prototype.getInternal = function (token, notFoundResult) {
        ...
        if ((token === import39.windowToken)) {
            return this._windowToken_26;
        }
        ...

When the same service is used as useFactory, everything is ok:

export function windowFactory() {
  return window;
}
export const windowProvider = { provide: windowToken, useFactory: windowFactory };

What exactly is wrong with using window as useValue provider here? Is it a known pitfall? Does this limitation apply to all globals or all useValue providers?


Solution

  • I was having a similar problem but it was with a SignalrWindow. The concept and the error though was identical.

    I then found this article here (https://blog.sstorie.com/integrating-angular-2-and-signalr-part-2-of-2/), and there were some comments at the bottom of the article that helped me solve the problem.

    Basically, it boils done to using a factory method instead of a useValue in the providers. I'm not sure why it's a problem, but I do know that this approach solves the aot problem.

    The steps to fix:

    Create a function that is exported

    export function windowFactory(): any {
        return window;
    }
    

    Then in the core module, in @NgModule in the providers you can do this:

    ...
    providers: [
        { provide: SignalrWindow, useFactory: windowFactory }
      ]
    ...
    

    Basically, you can rename the methods however you like (so, in your example it would be:)

    export const windowProvider = { provide: windowToken, useFactory: windowFactory };