angular - Como injetar window em um serviço?
Estou escrevendo um serviço Angular 2 em TypeScript que fará uso de localstorage. Quero injetar uma referência ao objeto window do navegador no meu serviço, já que não quero referenciar nenhuma variável global como o Angular 1.x $window.
Como eu faço isso?
Resposta #1
Isso está funcionando para mim atualmente (03-2018, angular 5.2 com AoT, testado no angular-cli e em uma build personalizada do webpack):
Primeiro, crie um serviço injetável que forneça uma referência para a janela:
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();
}
}
Agora, registre esse serviço no seu AppModule raiz para que ele possa ser injetado em qualquer lugar:
import { WindowRefService } from './window-ref.service';
@NgModule({
providers: [
WindowRefService
],
...
})
export class AppModule {}
e depois, mais adiante, quando você precisar injetar 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;
}
...
Você também pode desejar adicionar nativeDocument e outras variáveis globais a este serviço de forma semelhante se você as utilizar em sua aplicação.
editar: Atualizado com a sugestão do Truchainz. edit2: Atualizado para angular 2.1.2 edit3: Adicionadas notas sobre AoT edit4: Adicionando nota sobre a solução alternativa de tipo any edit5: Solução atualizada para usar um WindowRefService que corrige um erro que eu estava recebendo ao usar a solução anterior com uma build diferente edit6: adicionando exemplo de tipagem personalizada para Window
Resposta #2
Você pode obter a janela a partir do documento injetado.
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);
}
}
Se você tiver propriedades personalizadas na window, pode usar sua própria 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);
}
}
Resposta #3
Com o lançamento do Angular 2.0.0-rc.5, o NgModule foi introduzido. A solução anterior parou de funcionar para mim. Isto é o que eu fiz para consertar:
app.module.ts:
@NgModule({
providers: [
{ provide: 'Window', useValue: window }
],
declarations: [...],
imports: [...]
})
export class AppModule {}
Em algum componente:
import { Component, Inject } from '@angular/core';
@Component({...})
export class MyComponent {
constructor (@Inject('Window') window: Window) {}
}
Você também poderia usar um OpaqueToken em vez da string 'Window'
Editar :
O AppModule é usado para inicializar seu aplicativo em main.ts assim:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
Para mais informações sobre o NgModule, leia a documentação do Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html
Resposta #4
Você pode simplesmente injetar depois de definir o provedor:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
Resposta #5
Para fazê-lo funcionar no Angular 2.1.1 eu tive que @Inject window usando uma string
constructor( @Inject('Window') private window: Window) { }
e então zombar disso assim
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
e no @NgModule comum eu forneço assim
{ provide: 'Window', useValue: window }
Resposta #6
Aqui está outra solução que surgiu recentemente depois que eu me cansei de obter defaultView do token embutido DOCUMENT e verificá-lo quanto a 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;
}
});
Nota: Eu disponibilizei este token junto com muitos outros acessadores de objetos globais como uma pequena biblioteca disponível aqui: https://github.com/ng-web-apis/common
Confira o restante da organização para muitas outras bibliotecas de API nativas para Angular: https://github.com/ng-web-apis
Resposta #7
Antes da declaração @Component, você também pode fazer isso,
declare var window: any;
O compilador na verdade permitirá que você acesse a variável global window agora, já que você a declarou como uma variável global assumida com tipo any.
Eu não recomendaria acessar o window em todo lugar na sua aplicação, no entanto, você deve criar serviços que acessem/modifiquem os atributos necessários do window (e injetar esses serviços em seus componentes) para delimitar o que você pode fazer com o window sem permitir que eles modifiquem o objeto window inteiro.
Resposta #8
No Angular RC4, o seguinte funciona, que é uma combinação de algumas das respostas acima; no seu app.ts raiz, adicione aos providers:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
Então, no seu serviço etc, injete-o no construtor
constructor(
@Inject(Window) private _window: Window,
)
