Respostas:
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 nativeDocument
outros 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 any
tipo 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
@Inject
eu estivesse recebendo No provider for Window
erros. É muito bom não precisar do manual @Inject
!
@Inject(Window)
para isso funcionar
window
, mas com o serviço intermediário, ele permite o stub para fora do window
material 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.
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
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
}
window.var
conteúdo da página não muda
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);
}
}
Para fazê-lo funcionar no Angular 2.1.1 eu tive que @Inject
usar 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 @NgModule
eu forneço assim
{ provide: 'Window', useValue: window }
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,
)
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.
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_PROVIDERS
no 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 {}
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;
}
}
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) { }
Aqui está uma outra solução que eu vim recentemente depois eu me cansei de ficar defaultView
de DOCUMENT
built-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;
}
});
@Inject(WINDOW) private _window: any
e usá-lo como o token de injeção DOCUMENT fornecido pelo Angular?
É o suficiente para fazer
export class AppWindow extends Window {}
e fazer
{ provide: 'AppWindow', useValue: window }
para fazer AOT feliz
Existe a oportunidade de acesso direto ao objeto da janela através do documento
document.defaultView == window
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.
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);
}
}
Você pode usar NgZone no Angular 4:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
Também é uma boa ideia marcar o DOCUMENT
como 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 DOCUMENT
para 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
);
@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.
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 self
palavra-chave que também aponta para o objeto janela.
Mantenha a simplicidade, pessoal!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
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 );
}
}
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.