What's the difference between providing and injecting 'Window' vs Window in Angular 8 and 9?
In order for your app to work with Server Side Rendering I suggest you not only use window through token, but also create this token in SSR friendly manner, without referencing window
at all. Angular has built-in DOCUMENT
token for accessing document
. Here's what I came up with for my projects to use window
through tokens:
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;
},
},
);
Edit: Since this is something people often need, we've used this technique to create a tiny open-source library with injection tokens for global objects, so you can use it:
https://github.com/ng-web-apis/common
It has a sister library for mocks to be used with SSR in Angular Universal:
https://github.com/ng-web-apis/universal
Overall, check out our hub for native APIs in Angular:
https://ng-web-apis.github.io/
Considering the ValueProvider
interface:
export declare interface ValueProvider extends ValueSansProvider {
/**
* An injection token. Typically an instance of `Type` or `InjectionToken`, but can be `any`.
*/
provide: any;
/**
* When true, injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean;
}
The provide
property is of type any
. That means any object (included the Window
constructor) can go inside it. The object actually doesn't matter, only the reference matters to identify which provider should be used to inject a parameter in a constructor.
It should not be considered as a good practice to use the native Window
constructor as an injection token. It fails at compile time because Window
exists at run time in a browser environment, it also exists as a TypeScript declare
but the Angular 8 compiler cannot do static code analysis to correlate the Window
in the providers and the Window
in a constructor's parameters, since the assignment of Window
is done by the browser, not by the code. Not sure why it works in Angular 9, though...
You should create your own injection token that represents the dependency provider. This injection token should be either:
- A dedicated string (like you did with
'Window'
) - A dedicated
InjectionToken
. For exampleexport const window = new InjectionToken<Window>('window');
Moreover, the Angular code should be platform agnostic (should be executable in a browser and on a Node.js server as well) so it would be better to use a factory that returns window
or undefined
/null
, then handle the undefined
/null
case in the components.