142

I'm writing an Angular 2 service in TypeScript that will make use of localstorage. I want to inject a reference to the browser window object into my service since I don't want to reference any global variables like Angular 1.x $window.

How do I do that?

Jayakumar Thangavel
  • 1,884
  • 1
  • 22
  • 29
lokanx
  • 1,429
  • 2
  • 10
  • 5

23 Answers23

143

This is working for me currently (2018-03, angular 5.2 with AoT, tested in angular-cli and a custom webpack build):

First, create an injectable service that provides a reference to window:

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

// This interface is optional, showing how you can add strong typings for custom globals.
// Just use "Window" as the type if you don't have custom global stuff
export interface ICustomWindow extends Window {
    __custom_global_stuff: string;
}

function getWindow (): any {
    return window;
}

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

Now, register that service with your root AppModule so it can be injected everywhere:

import { WindowRefService } from './window-ref.service';

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

and then later on where you need to inject window:

import { Component} from '@angular/core';
import { WindowRefService, ICustomWindow } from './window-ref.service';

@Component({ ... })
export default class MyCoolComponent {
    private _window: ICustomWindow;

    constructor (
        windowRef: WindowRefService
    ) {
        this._window = windowRef.nativeWindow;
    }

    public doThing (): void {
        let foo = this._window.XMLHttpRequest;
        let bar = this._window.__custom_global_stuff;
    }
...

You may also wish to add nativeDocument and other globals to this service in a similar way if you use these in your application.


edit: Updated with Truchainz suggestion. edit2: Updated for angular 2.1.2 edit3: Added AoT notes edit4: Adding any type workaround note edit5: Updated solution to use a WindowRefService which fixes an error I was getting when using previous solution with a different build edit6: adding example custom Window typing

elwyn
  • 10,360
  • 11
  • 42
  • 52
  • 1
    Having the @Inject in the constructor parameters threw a bunch of errors for me such as `ORIGINAL EXCEPTION: No provider for Window!`. However, removing it fixed the problem for me. Using just the first 2 global lines was sufficient for me. – TrieuNomad Jun 16 '16 at 23:29
  • Interesting ^^ I will have to try it in a couple more demo projects - without the `@Inject` I was getting `No provider for Window` errors. That's pretty nice not needing the manual `@Inject`! – elwyn Jun 17 '16 at 01:57
  • On 2.1.2 I had to use `@Inject(Window)` for this to work – James Kleeh Nov 15 '16 at 16:55
  • Using this solution (both with or without @inject), when I try to compile my app with the ngc compiler (aot) I get the following error: `Could not resolve type Window`. With the tsc compiler I get no error. FYI, I try to use Window in a service, not a component. – j3r6me Nov 17 '16 at 14:06
  • Not working.. I'm getting `metadata_resolver.js:623Uncaught Error: Can't resolve all parameters for NewComponent: ( ?)` – Murhaf Sousli Nov 25 '16 at 01:19
  • _you could probably write yourself an interface if you want better typing than `any`_. **Note:** You **can't** use an interface as a type in angular DI – Trash Can Dec 26 '16 at 21:21
  • @Dummy I believe you *CAN* use an interface as a type in Angular DI if you are using `@Inject` to explicitly provide a type or key to resolve the DI from. E.g. above where it has `@Inject('Window')` that tells Angular WHAT to inject, so at that point the type you give is irrelevant to Angular, so you could happily use `IWindow` – elwyn Dec 26 '16 at 22:13
  • 1
    https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#dependency-injection-tokens. Oh my bad, didn't read carefully – Trash Can Dec 26 '16 at 22:19
  • @elwyn I don't get error in AoT but a warning: `export 'window' (imported as 'import1') was not found in './app.module'` and the app doesn't work once I open it in the browser :/ – Dunos Dec 29 '16 at 15:40
  • 2
    @Brian yes, it's still accessing `window`, but with the service in between it allows stubbing out native `window` stuff in unit tests, and as you mention for SSR an alternate service can be provided which exposes a mock/noop window for the server. The reason I mention AOT is several of the early solutions for wrapping window broke in AOT when Angular updated. – elwyn Jan 10 '18 at 18:55
80

You can get window from injected document.

import { Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

export class MyClass {

  private window: Window;

  constructor(@Inject(DOCUMENT) private document: Document) {
     this.window = this.document.defaultView;
  }

  check() {
    console.log(this.document);
    console.log(this.window);
  }

}

If you have custom properties on window, you can use your own interface

import { Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

export interface CustomWindow extends Window {
  $: JQuery
  customProperty: boolean;
}

export class MyClass {

  private window: CustomWindow;

  constructor(@Inject(DOCUMENT) private document: Document) {
     this.window = <any>this.document.defaultView;
  }

  check() {
    console.log(this.document);
    console.log(this.window);
  }

}
Alex Nikulin
  • 8,194
  • 4
  • 35
  • 37
  • 6
    This is the correct answer in 2021... although you have to delcare `this.window` first – Jonathan Apr 25 '21 at 14:36
  • 1
    I got an error "(property) MainComponent.window: Window Type '(Window & typeof globalThis) | null' is not assignable to type 'Window'. Type 'null' is not assignable to type 'Window'." How do I go about this? – syahiruddin Jun 14 '22 at 11:43
  • This is the correct way to do it. – RcoderNY Jun 22 '22 at 01:02
  • When accessing a property on the this.window object, I get an immediate error: "Property '________' does not exist on type 'Window'.ts(2339)" where ______ is the property to access. I tried this on multiple window properties I've observed on the console log. – Eric Feb 03 '23 at 21:16
  • @TheWebDev Try this – Alex Nikulin Feb 04 '23 at 08:02
40

With the release of angular 2.0.0-rc.5 NgModule was introduced. The previous solution stopped working for me. This is what I did to fix it:

app.module.ts:

@NgModule({        
  providers: [
    { provide: 'Window',  useValue: window }
  ],
  declarations: [...],
  imports: [...]
})
export class AppModule {}

In some component:

import { Component, Inject } from '@angular/core';

@Component({...})
export class MyComponent {
    constructor (@Inject('Window') window: Window) {}
}

You could also use an OpaqueToken instead of the string 'Window'

Edit:

The AppModule is used to bootstrap your application in main.ts like this:

import { platformBrowserDynamic  } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)

For more information about NgModule read the Angular 2 documentation: https://angular.io/docs/ts/latest/guide/ngmodule.html

JNK
  • 796
  • 6
  • 12
24

You can just inject it after you've set the provider:

import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);

constructor(private window: Window) {
    // this.window
}
Paul Dutka
  • 241
  • 1
  • 3
  • but when i change the `window.var` the content of page doesn't change – Ravinder Payal May 12 '16 at 05:17
  • 8
    This did not work in Safari as Window is not Injectable. I had to create my own Injectable type which contained the properties of Window that I required. A better approach may have been to to create a service as described in the other answers – daveb May 27 '16 at 10:07
  • 1
    This approach does not work, because useValue actually creates a copy of the value you provide for it. See: https://github.com/angular/angular/issues/10788#issuecomment-300614425. This approach would work if you changed it to use useFactory and return the value from a callback. – Levi Lindsey Nov 14 '17 at 00:33
19

To get it to work on Angular 2.1.1 I had to @Inject window using a string

  constructor( @Inject('Window') private window: Window) { }

and then mock it like this

beforeEach(() => {
  let windowMock: Window = <any>{ };
  TestBed.configureTestingModule({
    providers: [
      ApiUriService,
      { provide: 'Window', useFactory: (() => { return windowMock; }) }
    ]
  });

and in the ordinary @NgModule I provide it like this

{ provide: 'Window', useValue: window }
Klas Mellbourn
  • 42,571
  • 24
  • 140
  • 158
17

Here's another solution I came up recently after I got tired of getting defaultView from DOCUMENT built-in token and checking it for null:

import {DOCUMENT} from '@angular/common';
import {inject, InjectionToken} from '@angular/core';

export const WINDOW = new InjectionToken<Window>(
    'An abstraction over global window object',
    {
        factory: () => {
            const {defaultView} = inject(DOCUMENT);

            if (!defaultView) {
                throw new Error('Window is not available');
            }

            return defaultView;
        }
    });

Note: I have released this token with many other global object accessors as a tiny library available here: https://github.com/ng-web-apis/common

Check out the rest of the org for many other native API libraries for Angular: https://github.com/ng-web-apis

waterplea
  • 3,462
  • 5
  • 31
  • 47
  • 1
    So, I put this in my providers folder (for instance) and then I use in my component's constructor this injection token? `@Inject(WINDOW) private _window: any` and use it like the DOCUMENT injection token provided by Angular? – Sparker73 Jan 04 '20 at 16:48
  • Yes, that's all there is to it. – waterplea Jan 09 '20 at 12:24
  • Yup. It works perfectly, tanks for this simple solution. – Sparker73 Jan 11 '20 at 01:10
12

Before the @Component declaration, you can do that too,

declare var window: any;

The compiler will actually let you then access the global window variable now since you declare it as an assumed global variable with type any.

I wouldn't suggest to access window everywhere in your application though, You should create services that access/modify the needed window attributes (and inject those services in your components) to scope what you can do with the window without letting them to modify the whole window object.

S.Galarneau
  • 2,194
  • 1
  • 24
  • 26
  • If you do server-side rendering, you code will be broken.Because on srver-side you haven't any window object, and you need inject your own. – Alex Nikulin Jul 29 '20 at 05:56
10

In Angular RC4 the following works which is a combination of some of the above answers, in your root app.ts add it the providers:

@Component({
    templateUrl: 'build/app.html',
    providers: [
        anotherProvider,
        { provide: Window, useValue: window }
    ]
})

Then in your service etc inject it into the constructor

constructor(
      @Inject(Window) private _window: Window,
)
Joel Davey
  • 2,363
  • 1
  • 21
  • 20
9

I used OpaqueToken for 'Window' string:

import {unimplemented} from '@angular/core/src/facade/exceptions';
import {OpaqueToken, Provider} from '@angular/core/index';

function _window(): any {
    return window;
}

export const WINDOW: OpaqueToken = new OpaqueToken('WindowToken');

export abstract class WindowRef {
    get nativeWindow(): any {
        return unimplemented();
    }
}

export class BrowserWindowRef extends WindowRef {
    constructor() {
        super();
    }
    get nativeWindow(): any {
        return _window();
    }
}


export const WINDOW_PROVIDERS = [
    new Provider(WindowRef, { useClass: BrowserWindowRef }),
    new Provider(WINDOW, { useFactory: _window, deps: [] }),
];

And used just to import WINDOW_PROVIDERS in bootstrap in Angular 2.0.0-rc-4.

But with the release of Angular 2.0.0-rc.5 I need to create a separate module:

import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';

@NgModule({
    providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }

and just defined in the imports property of my main app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { WindowModule } from './other/window.module';

import { AppComponent } from './app.component';

@NgModule({
    imports: [ BrowserModule, WindowModule ],
    declarations: [ ... ],
    providers: [ ... ],
    bootstrap: [ AppComponent ]
})
export class AppModule {}
Chyngyz
  • 198
  • 1
  • 8
7

Angular 4 introduce InjectToken, and they also create a token for document called DOCUMENT. I think this is the official solution and it works in AoT.

I use the same logic to create a small library called ngx-window-token to prevent doing this over and over.

I have used it in other project and build in AoT without issues.

Here is how I used it in other package

Here is the plunker

In your module

imports: [ BrowserModule, WindowTokenModule ] In your component

constructor(@Inject(WINDOW) _window) { }

maxisam
  • 21,975
  • 9
  • 75
  • 84
7

It is enough to do

export class AppWindow extends Window {} 

and do

{ provide: 'AppWindow', useValue: window } 

to make AOT happy

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
6

As of today (April 2016), the code in the previous solution doesn't work, I think it is possible to inject window directly into App.ts and then gather the values you need into a service for global access in the App, but if you prefer to create and inject your own service, a way simpler solution is this.

https://gist.github.com/WilldelaVega777/9afcbd6cc661f4107c2b74dd6090cebf

//--------------------------------------------------------------------------------------------------
// Imports Section:
//--------------------------------------------------------------------------------------------------
import {Injectable} from 'angular2/core'
import {window} from 'angular2/src/facade/browser';

//--------------------------------------------------------------------------------------------------
// Service Class:
//--------------------------------------------------------------------------------------------------
@Injectable()
export class WindowService
{
    //----------------------------------------------------------------------------------------------
    // Constructor Method Section:
    //----------------------------------------------------------------------------------------------
    constructor(){}

    //----------------------------------------------------------------------------------------------
    // Public Properties Section:
    //----------------------------------------------------------------------------------------------
    get nativeWindow() : Window
    {
        return window;
    }
}
Will de la Vega
  • 536
  • 1
  • 5
  • 17
4

This is the shortest/cleanest answer that I've found working with Angular 4 AOT

Source: https://github.com/angular/angular/issues/12631#issuecomment-274260009

@Injectable()
export class WindowWrapper extends Window {}

export function getWindow() { return window; }

@NgModule({
  ...
  providers: [
    {provide: WindowWrapper, useFactory: getWindow}
  ]
  ...
})
export class AppModule {
  constructor(w: WindowWrapper) {
    console.log(w);
  }
}
nwarp
  • 731
  • 4
  • 8
  • 17
4

There is an opportunity for direct access to the object of window through the document

document.defaultView == window
Vasyl Petrov
  • 360
  • 2
  • 9
3

I know the question is how to inject the window object into a component but you're doing this just to get to localStorage it seems. If you realy just want localStorage, why not use a service that exposes just that, like h5webstorage. Then you component will describe its real dependencies which makes your code more readable.

SirDarquan
  • 81
  • 2
  • 2
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Uyghur Lives Matter Apr 28 '16 at 18:52
3

It's also a good idea to mark the DOCUMENT as optional. Per the Angular docs:

Document might not be available in the Application Context when Application and Rendering Contexts are not the same (e.g. when running the application into a Web Worker).

Here's an example of using the DOCUMENT to see whether the browser has SVG support:

import { Optional, Component, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common'

...

constructor(@Optional() @Inject(DOCUMENT) document: Document) {
   this.supportsSvg = !!(
   document &&
   document.createElementNS &&
   document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect
);
Ole
  • 41,793
  • 59
  • 191
  • 359
2

You can use NgZone on Angular 4:

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

constructor(private zone: NgZone) {}

print() {
    this.zone.runOutsideAngular(() => window.print());
}
Leonardo Pinto
  • 309
  • 3
  • 7
2

Declare a provider into your module:

  providers: [
    { provide: Window, useValue: window }
  ]

Into a component or service, inject:

constructor(private window: Window)

You may access properties:

console.log(this.window.document);

Or create new values:

(<any>this.window).MyNewValue = 'John Doe' ;
Andre Mesquita
  • 879
  • 12
  • 25
0

@maxisam thanks for ngx-window-token. I was doing something similar but switched to yours. This is my service for listening to window resize events and notifying subscribers.

import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import { WINDOW } from 'ngx-window-token';


export interface WindowSize {
    readonly width: number;
    readonly height: number;
}

@Injectable()
export class WindowSizeService {

    constructor( @Inject(WINDOW) private _window: any ) {
        Observable.fromEvent(_window, 'resize')
        .auditTime(100)
        .map(event => <WindowSize>{width: event['currentTarget'].innerWidth, height: event['currentTarget'].innerHeight})
        .subscribe((windowSize) => {
            this.windowSizeChanged$.next(windowSize);
        });
    }

    readonly windowSizeChanged$ = new BehaviorSubject<WindowSize>(<WindowSize>{width: this._window.innerWidth, height: this._window.innerHeight});
}

Short and sweet and works like a charm.

Andrew Alderson
  • 968
  • 5
  • 16
0

If you need to inject the window because you do really need properties belonging to the "window", just create a service as follow

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

@Injectable({
  providedIn: 'root',
})
export class WindowService {
  public getInstance(): Window {
    return window;
  }
}

Here is an example where it becomes easy to mock WindowService for testing purpose:

export class AnotherService {
  constructor(private windowService: WindowService) {}

  public hasPreviousPage(): boolean {
    return this.windowService.getInstance().history.length > 2;
  }
}

If however you are using window to then get a globally defined variable, I would recommend at first using globalThis instead. Then, typing the global is a matter of typescript module, in short: Do something like this:

Declare this ambient context this somewhere

declare global {
  function hello():string;
}

Then ts won't complain for the following code:

globalThis.hello(); // best way
window.hello(); // okay but window could be undefined in tests, workers or headless nodejs

Note that you still need to add the implementation of hello() globally somewhere.

Of course you could also (but I DO NOT recommend) use the following dirty trick:

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

interface ExtraParams {
hello:() => string;
}

@Injectable({
  providedIn: 'root',
})
export class WindowService {
  public getInstance(): Window & ExtraParams {
    return window as unknown as Window & ExtraParams;
  }
}
Flavien Volken
  • 19,196
  • 12
  • 100
  • 133
-1

Getting window object via DI(Dependency Injection) is not a good idea when global variables are accessible throughout the application.

But if you don't want to use window object then you can also use self keyword which also points to window object.

Shivang Gupta
  • 3,139
  • 1
  • 25
  • 24
  • 4
    That's not good advice. Dependency Injection makes classes (components, directives, services, pipes, ...) easier to test (for example even without a browser) and easier to reuse on different platforms like server-side rendering or Web Workers. It might work for some and the simplicity might have some appeal, but discouraging using DI is IMHO a bad answer. – Günter Zöchbauer Jan 31 '18 at 10:43
  • If you do server-side rendering, you code will be broken.Because on srver-side you haven't any window object, and you need inject your own. – Alex Nikulin Jul 29 '20 at 05:55
-1

Keep it simple, folks!

export class HeroesComponent implements OnInit {
  heroes: Hero[];
  window = window;
}

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
geoyws
  • 3,326
  • 3
  • 35
  • 47
  • If you do server-side rendering, you code will be broken.Because on srver-side you haven't any window object, and you need inject your own. – Alex Nikulin Jul 29 '20 at 05:54
-2

Actually its very simple to access window object here is my basic component and i tested it its working

import { Component, OnInit,Inject } from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';

@Component({
  selector: 'app-verticalbanners',
  templateUrl: './verticalbanners.component.html',
  styleUrls: ['./verticalbanners.component.css']
})
export class VerticalbannersComponent implements OnInit {

  constructor(){ }

  ngOnInit() {
    console.log(window.innerHeight );
  }

}
Vikas Kandari
  • 1,612
  • 18
  • 23
  • If you do server-side rendering, you code will be broken.Because on srver-side you haven't any window object, and you need inject your own. – Alex Nikulin Jul 29 '20 at 05:54