Como injetar janela em um serviço?


111

Estou escrevendo um serviço Angular 2 em TypeScript que fará uso de localstorage. Quero injetar uma referência para o navegador windowobjeto em meu serviço desde que eu não quero fazer referência a quaisquer variáveis globais como 1.x angular $window.

Como faço isso?

Respostas:


135

Isso está funcionando para mim atualmente (2018-03, angular 5.2 com AoT, testado no angular-cli e uma construção de webpack customizada):

Primeiro, crie um serviço injetável que forneça uma referência à 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 com 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 mais tarde onde você precisa 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 nativeDocumentoutros globais a este serviço de maneira semelhante, se os usar em seu aplicativo.


editar: Atualizado com sugestão de Truchainz. edit2: Atualizado para angular 2.1.2 edit3: Adicionadas notas AoT edit4: Adicionando anytipo de nota alternativa edit5: Solução atualizada para usar um WindowRefService que corrige um erro que eu estava recebendo ao usar a solução anterior com uma construção diferente edit6: adicionando exemplo de digitação de janela personalizada


1
Ter o @Inject nos parâmetros do construtor gerou um monte de erros para mim, como ORIGINAL EXCEPTION: No provider for Window!. No entanto, removê-lo resolveu o problema para mim. Usar apenas as 2 primeiras linhas globais foi suficiente para mim.
— TrieuNomad de

Interessante ^^ Vou ter que tentar em mais alguns projetos de demonstração - sem que @Injecteu estivesse recebendo No provider for Windowerros. É muito bom não precisar do manual @Inject!
— elwyn

Em 2.1.2 eu tive que usar @Inject(Window)para isso funcionar
— James Kleeh

1
angular.io/docs/ts/latest/guide/… . Ai, que pena, não li com atenção
— Teedeez

2
@Brian sim, ainda está acessando window, mas com o serviço intermediário, ele permite o stub para fora do windowmaterial nativo em testes de unidade, e como você mencionou para SSR, um serviço alternativo pode ser fornecido que expõe uma janela mock / noop para o servidor. A razão pela qual menciono AOT é que várias das primeiras soluções para janela de empacotamento quebraram no AOT quando o Angular foi atualizado.
— elwyn de

34

Com o lançamento do angular 2.0.0-rc.5, o NgModule foi introduzido. A solução anterior parou de funcionar para mim. Isso é 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 pode usar um OpaqueToken em vez da string 'Window'

Editar:

O AppModule é usado para inicializar seu aplicativo em main.ts como este:

import { platformBrowserDynamic  } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)

Para obter mais informações sobre o NgModule, leia a documentação do Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html


19

Você pode simplesmente injetá-lo depois de definir o provedor:

import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);

constructor(private window: Window) {
    // this.window
}

mas quando eu mudo o window.varconteúdo da página não muda
— Ravinder Payal

6
Isso não funcionou no Safari porque a janela não é injetável. Eu tive que criar meu próprio tipo injetável que continha as propriedades de Window que eu precisava. Uma abordagem melhor pode ter sido criar um serviço conforme descrito nas outras respostas
— daveb

Essa abordagem não funciona, porque useValue, na verdade, cria uma cópia do valor fornecido por você. Consulte: github.com/angular/angular/issues/10788#issuecomment-300614425 . Essa abordagem funcionaria se você alterasse para usar useFactory e retornasse o valor de um retorno de chamada.
— Levi Lindsey

18

Você pode obter a janela do documento injetado.

import { Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

export class MyClass {

  constructor(@Inject(DOCUMENT) private document: Document) {
     this.window = this.document.defaultView;
  }

  check() {
    console.log(this.document);
    console.log(this.window);
  }

}

15

Para fazê-lo funcionar no Angular 2.1.1 eu tive que @Injectusar uma janela usando uma string

  constructor( @Inject('Window') private window: Window) { }

e então zombar assim

beforeEach(() => {
  let windowMock: Window = <any>{ };
  TestBed.configureTestingModule({
    providers: [
      ApiUriService,
      { provide: 'Window', useFactory: (() => { return windowMock; }) }
    ]
  });

e no normal @NgModuleeu forneço assim

{ provide: 'Window', useValue: window }

10

No Angular RC4, o seguinte funciona, que é uma combinação de algumas das respostas acima, em seu app.ts raiz, adicione os provedores:

@Component({
    templateUrl: 'build/app.html',
    providers: [
        anotherProvider,
        { provide: Window, useValue: window }
    ]
})

Então, em seu serviço etc injete-o no construtor

constructor(
      @Inject(Window) private _window: Window,
)

10

Antes da declaração @Component, você também pode fazer isso,

declare var window: any;

O compilador permitirá que você acesse a variável global de janela agora, já que você a declara como uma variável global assumida com o tipo qualquer.

Eu não sugeriria acessar a janela em qualquer lugar em seu aplicativo, você deve criar serviços que acessem / modifiquem os atributos de janela necessários (e injete esses serviços em seus componentes) para definir o que você pode fazer com a janela sem permitir que eles modifiquem o objeto de janela inteira.


Se você fizer a renderização do lado do servidor, seu código será quebrado. Porque no lado srver você não tem nenhum objeto de janela e precisa injetar o seu próprio.
— Alex Nikulin

9

Eu usei o OpaqueToken para a string 'Window':

import {unimplemented} from '@angular/core/src/facade/exceptions';
import {OpaqueToken, Provider} from '@angular/core/index';

function _window(): any {
    return window;
}

export const WINDOW: OpaqueToken = new OpaqueToken('WindowToken');

export abstract class WindowRef {
    get nativeWindow(): any {
        return unimplemented();
    }
}

export class BrowserWindowRef extends WindowRef {
    constructor() {
        super();
    }
    get nativeWindow(): any {
        return _window();
    }
}


export const WINDOW_PROVIDERS = [
    new Provider(WindowRef, { useClass: BrowserWindowRef }),
    new Provider(WINDOW, { useFactory: _window, deps: [] }),
];

E usado apenas para importar WINDOW_PROVIDERSno bootstrap no Angular 2.0.0-rc-4.

Mas com o lançamento do Angular 2.0.0-rc.5, preciso criar um módulo separado:

import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';

@NgModule({
    providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }

e apenas definido na propriedade de importações do meu principal app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { WindowModule } from './other/window.module';

import { AppComponent } from './app.component';

@NgModule({
    imports: [ BrowserModule, WindowModule ],
    declarations: [ ... ],
    providers: [ ... ],
    bootstrap: [ AppComponent ]
})
export class AppModule {}

6

A partir de hoje (abril de 2016), o código da solução anterior não funciona, acho que é possível injetar janela diretamente no App.ts e, em seguida, reunir os valores necessários em um serviço de acesso global no aplicativo, mas se você preferir criar e injetar seu próprio serviço, uma solução bem mais simples é esta.

https://gist.github.com/WilldelaVega777/9afcbd6cc661f4107c2b74dd6090cebf

//--------------------------------------------------------------------------------------------------
// Imports Section:
//--------------------------------------------------------------------------------------------------
import {Injectable} from 'angular2/core'
import {window} from 'angular2/src/facade/browser';

//--------------------------------------------------------------------------------------------------
// Service Class:
//--------------------------------------------------------------------------------------------------
@Injectable()
export class WindowService
{
    //----------------------------------------------------------------------------------------------
    // Constructor Method Section:
    //----------------------------------------------------------------------------------------------
    constructor(){}

    //----------------------------------------------------------------------------------------------
    // Public Properties Section:
    //----------------------------------------------------------------------------------------------
    get nativeWindow() : Window
    {
        return window;
    }
}

6

O Angular 4 apresenta o InjectToken e também cria um token para o documento chamado DOCUMENT . Acho que essa é a solução oficial e funciona no AoT.

Eu uso a mesma lógica para criar uma pequena biblioteca chamada ngx-window-token para evitar fazer isso repetidamente.

Eu usei em outro projeto e construí no AoT sem problemas.

Aqui está como eu usei em outro pacote

Aqui está o êmbolo

Em seu módulo

imports: [ BrowserModule, WindowTokenModule ] Em seu componente

constructor(@Inject(WINDOW) _window) { }


5

Aqui está uma outra solução que eu vim recentemente depois eu me cansei de ficar defaultViewde DOCUMENTbuilt-in token e verifica se existem nulo:

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;
        }
    });

1
Então, eu coloco isso na minha pasta de fornecedores (por exemplo) e uso no construtor do meu componente esse token de injeção? @Inject(WINDOW) private _window: anye usá-lo como o token de injeção DOCUMENT fornecido pelo Angular?
— Sparker73

Sim, isso é tudo que há para fazer.
— água de

Sim. Funciona perfeitamente, tanques para esta solução simples.
— Sparker73

4

É o suficiente para fazer

export class AppWindow extends Window {} 

e fazer

{ provide: 'AppWindow', useValue: window } 

para fazer AOT feliz


4

Existe a oportunidade de acesso direto ao objeto da janela através do documento

document.defaultView == window

3

Eu sei que a questão é como injetar o objeto de janela em um componente, mas você está fazendo isso apenas para chegar a localStorage, ao que parece. Se você realmente quer apenas localStorage, por que não usar um serviço que expõe exatamente isso, como o h5webstorage . Em seguida, seu componente descreverá suas dependências reais, o que torna seu código mais legível.


2
Embora este link possa responder à pergunta, é melhor incluir as partes essenciais da resposta aqui e fornecer o link para referência. As respostas somente com link podem se tornar inválidas se a página vinculada mudar.
— Todos os trabalhadores são essenciais

3

Esta é a resposta mais curta / limpa que encontrei ao trabalhar com Angular 4 AOT

Fonte: https://github.com/angular/angular/issues/12631#issuecomment-274260009

@Injectable()
export class WindowWrapper extends Window {}

export function getWindow() { return window; }

@NgModule({
  ...
  providers: [
    {provide: WindowWrapper, useFactory: getWindow}
  ]
  ...
})
export class AppModule {
  constructor(w: WindowWrapper) {
    console.log(w);
  }
}

2

Você pode usar NgZone no Angular 4:

import { NgZone } from '@angular/core';

constructor(private zone: NgZone) {}

print() {
    this.zone.runOutsideAngular(() => window.print());
}

2

Também é uma boa ideia marcar o DOCUMENTcomo opcional. De acordo com os documentos Angular:

O documento pode não estar disponível no contexto do aplicativo quando os contextos de aplicativo e renderização não são os mesmos (por exemplo, ao executar o aplicativo em um Web Worker).

Aqui está um exemplo de uso do DOCUMENTpara ver se o navegador tem suporte para SVG:

import { Optional, Component, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common'

...

constructor(@Optional() @Inject(DOCUMENT) document: Document) {
   this.supportsSvg = !!(
   document &&
   document.createElementNS &&
   document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect
);

0

@maxisam obrigado por ngx-window-token . Eu estava fazendo algo semelhante, mas mudei para o seu. Este é o meu serviço para ouvir eventos de redimensionamento de janela e notificar assinantes.

import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import { WINDOW } from 'ngx-window-token';


export interface WindowSize {
    readonly width: number;
    readonly height: number;
}

@Injectable()
export class WindowSizeService {

    constructor( @Inject(WINDOW) private _window: any ) {
        Observable.fromEvent(_window, 'resize')
        .auditTime(100)
        .map(event => <WindowSize>{width: event['currentTarget'].innerWidth, height: event['currentTarget'].innerHeight})
        .subscribe((windowSize) => {
            this.windowSizeChanged$.next(windowSize);
        });
    }

    readonly windowSizeChanged$ = new BehaviorSubject<WindowSize>(<WindowSize>{width: this._window.innerWidth, height: this._window.innerHeight});
}

Curto e doce e funciona como um encanto.


0

Obter objeto de janela via DI (injeção de dependência) não é uma boa ideia quando as variáveis ​​globais estão acessíveis em todo o aplicativo.

Mas se você não quiser usar o objeto janela, você também pode usar a selfpalavra-chave que também aponta para o objeto janela.


3
Esse não é um bom conselho. A injeção de dependência torna as classes (componentes, diretivas, serviços, canais, ...) mais fáceis de testar (por exemplo, mesmo sem um navegador) e mais fáceis de reutilizar em diferentes plataformas, como renderização do lado do servidor ou Web Workers. Pode funcionar para alguns e a simplicidade pode ter algum apelo, mas desencorajar o uso de DI é IMHO uma má resposta.
— Günter Zöchbauer

Se você fizer a renderização do lado do servidor, seu código será quebrado. Porque no lado srver você não tem nenhum objeto de janela e precisa injetar o seu próprio.
— Alex Nikulin

-1

Mantenha a simplicidade, pessoal!

export class HeroesComponent implements OnInit {
  heroes: Hero[];
  window = window;
}

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>

Se você fizer a renderização do lado do servidor, seu código será quebrado. Porque no lado srver você não tem nenhum objeto de janela e precisa injetar o seu próprio.
— Alex Nikulin

-2

Na verdade, é muito simples de acessar o objeto janela, aqui está o meu componente básico e testei seu funcionamento

import { Component, OnInit,Inject } from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';

@Component({
  selector: 'app-verticalbanners',
  templateUrl: './verticalbanners.component.html',
  styleUrls: ['./verticalbanners.component.css']
})
export class VerticalbannersComponent implements OnInit {

  constructor(){ }

  ngOnInit() {
    console.log(window.innerHeight );
  }

}

Se você fizer a renderização do lado do servidor, seu código será quebrado. Porque no lado srver você não tem nenhum objeto de janela e precisa injetar o seu próprio.
— Alex Nikulin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.