Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between providing and injecting 'Window' vs Window in Angular 8 and 9?

I have two Angular projects using these versions:

  • 9.0.0-next.6
  • 8.1.0

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?

like image 739
lampshade Avatar asked Nov 15 '19 09:11

lampshade


People also ask

What does @inject do in Angular?

@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.

How many ways we can inject service in Angular?

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.

What is $window in Angular?

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.

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.


2 Answers

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/

like image 114
waterplea Avatar answered Oct 12 '22 03:10

waterplea


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 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.

like image 7
Guerric P Avatar answered Oct 12 '22 03:10

Guerric P