Respuestas:
Esto me funciona actualmente (2018-03, angular 5.2 con AoT, probado en angular-cli y una compilación de paquete web personalizado):
Primero, cree un servicio inyectable que proporcione una referencia a la ventana:
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();
}
}
Ahora, registre ese servicio con su AppModule raíz para que pueda inyectarse en todas partes:
import { WindowRefService } from './window-ref.service';
@NgModule({
providers: [
WindowRefService
],
...
})
export class AppModule {}
y luego donde necesitas inyectar 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;
}
...
También es posible que desee agregar nativeDocument
y otros globales a este servicio de una manera similar si los usa en su aplicación.
editar: actualizado con sugerencia de Truchainz. edit2: Actualizado para angular 2.1.2 edit3: Se agregaron notas de AoT edit4: Añadiendo el any
tipo de nota de solución alternativa edit5: Solución actualizada para usar un WindowRefService que corrige un error que recibía al usar la solución anterior con una compilación diferente edit6: agregando un ejemplo de escritura de ventana personalizada
@Inject
que aparecieran No provider for Window
errores. ¡Eso es bastante bueno no necesitar el manual @Inject
!
@Inject(Window)
para que esto funcionara
window
, pero con el servicio intermedio permite eliminar window
cosas nativas en pruebas unitarias, y como mencionas para SSR, se puede proporcionar un servicio alternativo que expone una ventana simulada / noop para el servidor. La razón por la que menciono AOT es que varias de las primeras soluciones para envolver la ventana se rompieron en AOT cuando se actualizó Angular.
Con el lanzamiento de angular 2.0.0-rc.5 se introdujo NgModule. La solución anterior dejó de funcionar para mí. Esto es lo que hice para solucionarlo:
app.module.ts:
@NgModule({
providers: [
{ provide: 'Window', useValue: window }
],
declarations: [...],
imports: [...]
})
export class AppModule {}
En algún componente:
import { Component, Inject } from '@angular/core';
@Component({...})
export class MyComponent {
constructor (@Inject('Window') window: Window) {}
}
También puede usar un OpaqueToken en lugar de la cadena 'Ventana'
Editar:
El AppModule se usa para arrancar su aplicación en main.ts de esta manera:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
Para obtener más información sobre NgModule, lea la documentación de Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html
Puede inyectarlo después de haber configurado el proveedor:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
window.var
contenido de la página no cambia
Puede obtener una ventana del documento inyectado.
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 que funcione en Angular 2.1.1 tuve que hacer una @Inject
ventana usando una cadena
constructor( @Inject('Window') private window: Window) { }
y luego burlarte de esto
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
y en lo ordinario @NgModule
lo proporciono así
{ provide: 'Window', useValue: window }
En Angular RC4 funciona lo siguiente, que es una combinación de algunas de las respuestas anteriores, en su aplicación raíz, agregue los proveedores:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
Luego, en su servicio, etc.inyectelo en el constructor
constructor(
@Inject(Window) private _window: Window,
)
Antes de la declaración @Component, también puede hacerlo,
declare var window: any;
El compilador en realidad le permitirá acceder a la variable de ventana global ahora, ya que la declara como una variable global asumida con el tipo any.
Sin embargo, no sugeriría acceder a la ventana en todas partes de su aplicación, debe crear servicios que accedan / modifiquen los atributos de ventana necesarios (e inyecte esos servicios en sus componentes) para determinar lo que puede hacer con la ventana sin permitirles modificar el todo el objeto de la ventana.
Solía OpaqueToken para la cadena 'Ventana':
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: [] }),
];
Y se usa solo para importar WINDOW_PROVIDERS
en bootstrap en Angular 2.0.0-rc-4.
Pero con el lanzamiento de Angular 2.0.0-rc.5 necesito crear un módulo separado:
import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';
@NgModule({
providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }
y acaba de definir en la propiedad de importaciones de mi 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 hoy (abril de 2016), el código de la solución anterior no funciona, creo que es posible inyectar la ventana directamente en App.ts y luego recopilar los valores que necesita en un servicio para el acceso global en la aplicación, pero si prefiere crear e inyectar su propio servicio, una solución mucho más sencilla es 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;
}
}
Angular 4 introduce InjectToken y también crean un token para el documento llamado DOCUMENTO . Creo que esta es la solución oficial y funciona en AoT.
Utilizo la misma lógica para crear una pequeña biblioteca llamada ngx-window-token para evitar hacer esto una y otra vez.
Lo he usado en otro proyecto y lo construí en AoT sin problemas.
Así es como lo usé en otro paquete
Aquí está el plunker
En tu módulo
imports: [ BrowserModule, WindowTokenModule ]
En tu componente
constructor(@Inject(WINDOW) _window) { }
Es suficiente para hacer
export class AppWindow extends Window {}
y hacer
{ provide: 'AppWindow', useValue: window }
para hacer feliz a AOT
Existe la posibilidad de acceder directamente al objeto de la ventana a través del documento.
document.defaultView == window
Aquí hay otra solución que se me ocurrió poco después de que llegué cansado de recibir defaultView
de DOCUMENT
una función de token y comprueba si existe alguna 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
y usarlo como el token de inyección de DOCUMENTO proporcionado por Angular?
Sé que la pregunta es cómo inyectar el objeto de la ventana en un componente, pero parece que estás haciendo esto solo para llegar a localStorage. Si realmente solo desea localStorage, ¿por qué no utilizar un servicio que exponga precisamente eso, como h5webstorage ? Luego, su componente describirá sus dependencias reales, lo que hace que su código sea más legible.
Esta es la respuesta más corta / limpia que he encontrado trabajando con Angular 4 AOT
Fuente: 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);
}
}
Puede usar NgZone en Angular 4:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
También es una buena idea marcar DOCUMENT
como opcional. Según los documentos de Angular:
Es posible que el documento no esté disponible en el contexto de la aplicación cuando los contextos de aplicación y de renderizado no son los mismos (por ejemplo, cuando se ejecuta la aplicación en un Web Worker).
A continuación, se muestra un ejemplo del uso de DOCUMENT
para ver si el navegador es compatible con 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 gracias por ngx-window-token . Estaba haciendo algo similar, pero cambié al tuyo. Este es mi servicio para escuchar eventos de cambio de tamaño de ventana y notificar a los suscriptores.
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});
}
Corto y dulce y funciona a las mil maravillas.
Obtener un objeto de ventana a través de DI (inyección de dependencia) no es una buena idea cuando las variables globales son accesibles en toda la aplicación.
Pero si no desea usar el objeto de ventana, también puede usar una self
palabra clave que también apunta al objeto de ventana.
¡Mantenlo simple, amigos!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
En realidad, es muy simple acceder al objeto de la ventana. Aquí está mi componente básico y lo probé.
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!
. Sin embargo, quitarlo solucionó el problema. Usar solo las 2 primeras líneas globales fue suficiente para mí.