Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 484
Estus Flask Avatar asked Apr 17 '17 05:04

Estus Flask


People also ask

How do you inject a window into a service?

To inject window into a service with Angular, we can inject document into our service. Then we get window from document . to inject document with @Inject(DOCUMENT) private document: Document in the constructor signature. Then we get window from this.

What is useExisting in ANGULAR?

The useExisting provider key lets you map one token to another. In effect, the first token is an alias for the service associated with the second token, creating two ways to access the same service object.

What is window object in angular?

A reference to the browser's window object. While window is globally available in JavaScript, it causes testability problems, because it is a global variable. In AngularJS we always refer to it through the $window service, so it may be overridden, removed or mocked for testing.


2 Answers

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 };
like image 54
Newteq Developer Avatar answered Oct 14 '22 03:10

Newteq Developer


During AoT compilation, angular CLI statically analyses code too generate ngmodule.factory file. It sees "useValue" and check if static value is available and puts it into ngmodule.factory. During compile time, this value is not available, so it leaves that provider value and hence, it is returned as "undefined" when injected into constuctor.

However, "useFactory" is provided for very same purpose where you don't know your value until runtime.

Due to this, useValue is not working in your scenario, but "useFactory" will work.

Bottom line: When AOT compilation, Use "useValue" only if you have static value like constant string or number. Else use "useFactory" to return run-time calculated object/values.

like image 1
Ashish Patel Avatar answered Oct 14 '22 02:10

Ashish Patel