Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Angular Window Provider

Where did my $window go? No problem, use this provider to inject the window into your Angular component.

Why?

Because we don’t want to reference the global Window object directly in our Angular component since our application may be used in Angular Universal, and it’s considered best practice to avoid directly referencing the global objects. While you may have no plans to use Angular Universal, this enables us to cleanly inject the browser’s native Window object into a component.

How is this different?

While there are plenty of solutions out there already that propose methods for indirectly accessing the Window object, I’ve found that many of them don’t actually perform any conditional verification that our Angular application is executing in the context of a browser, where we will have the Window object.

Here’s an example of a solution that I felt was inadequate:

import { Injectable } from '@angular/core';

function getWindow (): any {
    return window;
}

@Injectable()
export class WindowRefService {
  get nativeWindow (): any {
    return getWindow();
  }
}

A quick look at this indicates that we are not checking if the application is executing in the context of a browser. Further, we may want to support a context for tests, where we might want to mock some properties or methods on the Window object.

Proposed Solution

Here is a proposed solution for creating an extensible method for injecting the Window into our Angular components. I’ve put this into a src/app/core/services/window.service.ts file in my application:

import { isPlatformBrowser } from "@angular/common";
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core';

/* Create a new injection token for injecting the window into a component. */
export const WINDOW = new InjectionToken('WindowToken');

/* Define abstract class for obtaining reference to the global window object. */
export abstract class WindowRef {

  get nativeWindow(): Window | Object {
    throw new Error('Not implemented.');
  }

}

/* Define class that implements the abstract class and returns the native window object. */
export class BrowserWindowRef extends WindowRef {

  constructor() {
    super();
  }

  get nativeWindow(): Window | Object {
    return window;
  }

}

/* Create an factory function that returns the native window object. */
export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: Object): Window | Object {
  if (isPlatformBrowser(platformId)) {
    return browserWindowRef.nativeWindow;
  }
  return new Object();
}

/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */
const browserWindowProvider: ClassProvider = {
  provide: WindowRef,
  useClass: BrowserWindowRef
};

/* Create an injectable provider that uses the windowFactory function for returning the native window object. */
const windowProvider: FactoryProvider = {
  provide: WINDOW,
  useFactory: windowFactory,
  deps: [ WindowRef, PLATFORM_ID ]
};

/* Create an array of providers. */
export const WINDOW_PROVIDERS = [
  browserWindowProvider,
  windowProvider
];

A few things to note:

To use this, we need to add the WINDOW_PROVIDERS to our app.module.ts module decorator’s providers array:

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [WINDOW_PROVIDERS],
  bootstrap: [AppComponent],
})
export class AppModule {}

You may also have a CoreModule (perhaps with a different name) that is a module that contains all of the services, interceptors, models, etc. that is available to your application. If that is the case, just import the WINDOW_PROVIDERS in that module, which is then imported into your AppModule.

Then, we can use the WINDOW injection token to inject the window into our component:

import { WINDOW } from "../core/services/window.service";

export class IndexComponent {

  constructor(@Inject(WINDOW) private window: Window) {
    console.log(window);
  }

}

Here is an example showing the window.navigator.userAgent string: