Pesquisa de site

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,
)

Artigos relacionados