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.