I have two Angular projects using these versions:
In the version 9 I used this to provide and inject the window
obhject:
@NgModule({
providers: [
{
provide: Window,
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(@Inject(Window) private window: Window)
}
Which works fine.
Taking this approach to version 8 throwed warnings and errors during compilation:
Warning: Can't resolve all parameters for TestComponent …
I solved it by using single quotes, like this:
@NgModule({
providers: [
{
provide: 'Window',
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(@Inject('Window') private window: Window)
}
What is the difference between both version?
What is the difference in Angular 8 and 9 that causes this thing?
@Injectable() lets Angular know that a class can be used with the dependency injector. @Injectable() is not strictly required if the class has other Angular decorators on it or does not have any dependencies. What is important is that any class that is going to be injected with Angular is decorated.
There are three types of Dependency Injections in Angular, they are as follows: Constructor injection: Here, it provides the dependencies through a class constructor. Setter injection: The client uses a setter method into which the injector injects the dependency.
The $window service is a wrapper around window object, so that it will be easy to override, remove or mocked for testing. It is recommended to use $window service in AngularJS instead of global window object directly.
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.
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:
'Window'
)InjectionToken
. For example export 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With