dependency injection - injection - Angular2-Cómo inyectar ventana en un servicio angular2
servicios en angular 2 (21)
¡Mantenlo simple, amigos!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
Estoy escribiendo un servicio Angular2 en TypeScript que utilizará el almacenamiento local.
Y quiero inyectar una referencia al objeto de la ventana del navegador en mi servicio ya que no quiero hacer referencia a ninguna variable global.
Como angular 1.x
$window
.
¿Cómo puedo hacer eso?
@maxisam gracias por ngx-window-token . Estaba haciendo algo similar pero cambié a la tuya. 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 como un encanto.
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 reunir los valores que necesita en un servicio para acceso global en la aplicación, pero Si prefiere crear e inyectar su propio servicio, una solución más simple 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 DOCUMENT . 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 construyo en AoT sin problemas.
Así es como lo usé en otro paquete
Aquí está el plunker
En su modulo
imports: [ BrowserModule, WindowTokenModule ]
en su componente
constructor(@Inject(WINDOW) _window) { }
Antes de la declaración de @Component, también puede hacerlo,
declare var window: any;
El compilador realmente le permitirá acceder a la variable de ventana global ahora, ya que la declara como una variable global supuesta 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 inyectar esos servicios en sus componentes) para determinar qué puede hacer con la ventana sin permitir que modifiquen objeto de ventana completa.
Aquí hay otra solución que encontré recientemente después de que me cansé de obtener
defaultView
desde el token incorporado
DOCUMENT
y verificar que no sea 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;
}
});
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 lugar de la cadena ''Ventana''
Editar:
El AppModule se utiliza para iniciar 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
En Angular RC4, lo siguiente funciona, 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., inyecte en el constructor
constructor(
@Inject(Window) private _window: Window,
)
En realidad, es muy simple acceder al objeto de ventana aquí es mi componente básico y lo probé para que funcione
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 );
}
}
Es suficiente para hacer
export class AppWindow extends Window {}
y hacer
{ provide: ''AppWindow'', useValue: window }
hacer feliz a MUCHO
Esta es la respuesta más corta / más 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);
}
}
Esto está funcionando para mí 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 más adelante donde necesita inyectar la
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 manera similar si los usa en su aplicación.
editar: actualizado con la sugerencia de Truchainz.
edit2: Actualizado para angular 2.1.2 edit3: Se agregaron notas de AoT edit4: Agregar
any
tipo de nota de solución edit5: Solución actualizada para usar un WindowRefService que corrige un error que estaba obteniendo al usar una solución anterior con una compilación diferente edit6: agregar un ejemplo de escritura personalizada de Windows
Existe una oportunidad de acceso directo al objeto de la ventana a través del documento.
document.defaultView == window
Obtener objetos de ventana a través de DI (Inyección de dependencias) no es una buena idea cuando las variables globales son accesibles en toda la aplicación.
Pero si no desea usar un objeto de ventana, también puede usar
self
palabra clave
self
que también apunta a un objeto de ventana.
Para que funcione en Angular 2.1.1 tuve que
@Inject
ventana usando una cadena
constructor( @Inject(''Window'') private window: Window) { }
y luego burlarse así
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: ''Window'', useFactory: (() => { return windowMock; }) }
]
});
y en el
@NgModule
ordinario lo proporciono así
{ provide: ''Window'', useValue: window }
Puede obtener la 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);
}
}
Puede usar NgZone en Angular 4:
import { NgZone } from ''@angular/core'';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
Sé que la pregunta es cómo inyectar el objeto de ventana en un componente, pero lo estás haciendo solo para llegar a localStorage. Si realmente solo desea localStorage, ¿por qué no utilizar un servicio que exponga exactamente eso, como h5webstorage ? Luego, su componente describirá sus dependencias reales, lo que hace que su código sea más legible.
Simplemente puede inyectarlo después de configurar el proveedor:
import {provide} from ''angular2/core'';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
También es una buena idea marcar el
DOCUMENT
como opcional.
Por los documentos angulares:
Es posible que el documento no esté disponible en el contexto de la aplicación cuando la aplicación y los contextos de representación no son los mismos (por ejemplo, cuando se ejecuta la aplicación en un trabajador web).
Aquí hay un ejemplo del uso del
DOCUMENT
para ver si el navegador tiene soporte 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
);
OpaqueToken para la cadena ''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: [] }),
];
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 recién definido en la propiedad de importación de mi
app.module.ts
principal
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 {}