Risposte:
Questo sta funzionando per me attualmente (2018-03, angular 5.2 con AoT, testato in angular-cli e una build webpack personalizzata):
Innanzitutto, crea un servizio iniettabile che fornisca un riferimento alla finestra:
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();
}
}
Ora registra quel servizio con il tuo AppModule di root in modo che possa essere iniettato ovunque:
import { WindowRefService } from './window-ref.service';
@NgModule({
providers: [
WindowRefService
],
...
})
export class AppModule {}
e poi in seguito dove devi iniettare 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;
}
...
Potresti anche voler aggiungere nativeDocumente altri globali a questo servizio in modo simile se li usi nella tua applicazione.
modifica: aggiornato con il suggerimento di Truchainz. edit2: aggiornato per angolare 2.1.2 edit3: aggiunte note AoT edit4: aggiunta di anynote di soluzione alternativa edit5: soluzione aggiornata per utilizzare un WindowRefService che corregge un errore che stavo ottenendo quando utilizzavo la soluzione precedente con una build diversa edit6: aggiunta di esempio di digitazione personalizzata della finestra
@Injectche stavo ottenendo No provider for Windowerrori. È carino non aver bisogno del manuale @Inject!
@Inject(Window)perché funzionasse
window, ma con il servizio in mezzo consente di eliminare windowroba nativa negli unit test e, come hai detto per SSR, può essere fornito un servizio alternativo che espone una finestra mock / noop per il server. Il motivo per cui ho menzionato AOT è che molte delle prime soluzioni per il wrapping della finestra si sono interrotte in AOT quando Angular è stato aggiornato.
Con il rilascio di angular 2.0.0-rc.5 è stato introdotto NgModule. La soluzione precedente ha smesso di funzionare per me. Questo è quello che ho fatto per risolverlo:
app.module.ts:
@NgModule({
providers: [
{ provide: 'Window', useValue: window }
],
declarations: [...],
imports: [...]
})
export class AppModule {}
In alcuni componenti:
import { Component, Inject } from '@angular/core';
@Component({...})
export class MyComponent {
constructor (@Inject('Window') window: Window) {}
}
Puoi anche usare un OpaqueToken invece della stringa "Finestra"
Modificare:
L'AppModule viene utilizzato per avviare la tua applicazione in main.ts in questo modo:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
Per maggiori informazioni su NgModule leggi la documentazione di Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html
Puoi semplicemente iniettarlo dopo aver impostato il provider:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
window.varcontenuto della pagina non cambia
Puoi ottenere la finestra dal documento iniettato.
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);
}
}
Per farlo funzionare su Angular 2.1.1 ho dovuto @Injectfinestra usando una stringa
constructor( @Inject('Window') private window: Window) { }
e poi deriderlo in questo modo
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
e nell'ordinario @NgModulelo fornisco in questo modo
{ provide: 'Window', useValue: window }
In Angular RC4 funziona il seguente, che è una combinazione di alcune delle risposte precedenti, nella tua app di root. Aggiungi i provider:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
Quindi nel tuo servizio ecc. Iniettalo nel costruttore
constructor(
@Inject(Window) private _window: Window,
)
Prima della dichiarazione @Component, puoi farlo anche tu,
declare var window: any;
Il compilatore ti consentirà effettivamente di accedere alla variabile della finestra globale ora poiché la dichiari come una variabile globale presunta con tipo any.
Non suggerirei di accedere alla finestra ovunque nella tua applicazione, dovresti creare servizi che accedono / modificano gli attributi della finestra necessari (e iniettare quei servizi nei tuoi componenti) per definire cosa puoi fare con la finestra senza permettere loro di modificare il oggetto intero finestra.
Ho usato OpaqueToken per la stringa "Finestra":
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 usato solo per importare WINDOW_PROVIDERSin bootstrap in Angular 2.0.0-rc-4.
Ma con il rilascio di Angular 2.0.0-rc.5 ho bisogno di creare un modulo separato:
import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';
@NgModule({
providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }
e appena definito nella proprietà imports del mio main 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 {}
Ad oggi (aprile 2016), il codice nella soluzione precedente non funziona, penso sia possibile iniettare la finestra direttamente in App.ts e quindi raccogliere i valori necessari in un servizio per l'accesso globale nell'app, ma se preferisci creare e iniettare il tuo servizio, una soluzione più semplice è questa.
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 e crea anche un token per il documento chiamato DOCUMENT . Penso che questa sia la soluzione ufficiale e funziona in AoT.
Uso la stessa logica per creare una piccola libreria chiamata ngx-window-token per evitare di farlo più e più volte.
L'ho usato in altri progetti e costruito in AoT senza problemi.
Ecco come l'ho usato in un altro pacchetto
Ecco il plunker
Nel tuo modulo
imports: [ BrowserModule, WindowTokenModule ]
Nel tuo componente
constructor(@Inject(WINDOW) _window) { }
Ecco un'altra soluzione che ho trovato di recente dopo che mi sono stancato di ottenere defaultViewdal DOCUMENTtoken integrato e controllarlo per null:
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: anye usarlo come il token di iniezione DOCUMENT fornito da Angular?
È abbastanza da fare
export class AppWindow extends Window {}
e fai
{ provide: 'AppWindow', useValue: window }
per rendere felice AOT
C'è un'opportunità per l'accesso diretto all'oggetto della finestra attraverso il documento
document.defaultView == window
So che la domanda è come iniettare l'oggetto finestra in un componente ma lo stai facendo solo per arrivare a localStorage a quanto pare. Se vuoi davvero solo localStorage, perché non utilizzare un servizio che esponga proprio questo, come h5webstorage . Quindi il tuo componente descriverà le sue reali dipendenze che renderanno il tuo codice più leggibile.
Questa è la risposta più breve / pulita che ho trovato lavorando con 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);
}
}
Puoi usare NgZone su Angular 4:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
È anche una buona idea contrassegnare DOCUMENTcome opzionale. Secondo i documenti Angular:
Il documento potrebbe non essere disponibile nel contesto dell'applicazione quando i contesti dell'applicazione e di rendering non sono gli stessi (ad esempio, quando si esegue l'applicazione in un Web Worker).
Ecco un esempio di utilizzo di DOCUMENTper vedere se il browser ha il supporto 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 grazie per ngx-window-token . Stavo facendo qualcosa di simile ma sono passato alla tua. Questo è il mio servizio per ascoltare gli eventi di ridimensionamento delle finestre e avvisare gli iscritti.
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});
}
Breve e dolce e funziona come un fascino.
Ottenere l'oggetto finestra tramite DI (Dependency Injection) non è una buona idea quando le variabili globali sono accessibili in tutta l'applicazione.
Ma se non vuoi usare l'oggetto finestra, puoi anche usare la selfparola chiave che punta anche all'oggetto finestra.
Mantienilo semplice, gente!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
In realtà è molto semplice accedere all'oggetto finestra qui è il mio componente di base e l'ho testato sul suo funzionamento
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!. Tuttavia, rimuoverlo ha risolto il problema per me. Usare solo le prime 2 linee globali è stato sufficiente per me.