Réponses:
Cela fonctionne pour moi actuellement (2018-03, angular 5.2 avec AoT, testé dans angular-cli et une version webpack personnalisée):
Tout d'abord, créez un service injectable qui fournit une référence à window:
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();
}
}
Maintenant, enregistrez ce service avec votre AppModule racine afin qu'il puisse être injecté partout:
import { WindowRefService } from './window-ref.service';
@NgModule({
providers: [
WindowRefService
],
...
})
export class AppModule {}
puis plus tard où vous devez injecter 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;
}
...
Vous pouvez également souhaiter ajouter d' nativeDocument
autres globaux à ce service de la même manière si vous les utilisez dans votre application.
edit: Mis à jour avec la suggestion de Truchainz. edit2: Mise à jour pour angular 2.1.2 edit3: Ajout de notes AoT edit4: Ajout de any
note de contournement de type edit5: Mise à jour de la solution pour utiliser un WindowRefService qui corrige une erreur que j'obtenais lors de l'utilisation de la solution précédente avec une version différente edit6: ajout d'un exemple de saisie de fenêtre personnalisée
@Inject
je reçoive des No provider for Window
erreurs. C'est plutôt sympa de ne pas avoir besoin du manuel @Inject
!
@Inject(Window)
pour que cela fonctionne
window
, mais avec le service entre les deux, il permet de supprimer des window
éléments natifs dans les tests unitaires, et comme vous le mentionnez pour SSR, un service alternatif peut être fourni qui expose une fenêtre fictive / noop pour le serveur. La raison pour laquelle je mentionne AOT est que plusieurs des premières solutions pour envelopper la fenêtre se sont cassées dans AOT lors de la mise à jour Angular.
Avec la sortie d'angular 2.0.0-rc.5, NgModule a été introduit. La solution précédente a cessé de fonctionner pour moi. Voici ce que j'ai fait pour y remédier:
app.module.ts:
@NgModule({
providers: [
{ provide: 'Window', useValue: window }
],
declarations: [...],
imports: [...]
})
export class AppModule {}
Dans certains composants:
import { Component, Inject } from '@angular/core';
@Component({...})
export class MyComponent {
constructor (@Inject('Window') window: Window) {}
}
Vous pouvez également utiliser un OpaqueToken au lieu de la chaîne 'Window'
Éditer:
L'AppModule est utilisé pour démarrer votre application dans main.ts comme ceci:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
Pour plus d'informations sur NgModule, lisez la documentation Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html
Vous pouvez simplement l'injecter après avoir défini le fournisseur:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
window.var
contenu de la page ne change pas
Vous pouvez obtenir une fenêtre à partir du document injecté.
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);
}
}
Pour le faire fonctionner sur Angular 2.1.1, j'ai dû @Inject
fenêtre en utilisant une chaîne
constructor( @Inject('Window') private window: Window) { }
et puis se moquer de ça comme ça
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
et dans l'ordinaire @NgModule
je le fournis comme ça
{ provide: 'Window', useValue: window }
Dans Angular RC4, les travaux suivants, qui sont une combinaison de certaines des réponses ci-dessus, dans votre application racine, ajoutez-y les fournisseurs:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
Puis dans votre service, etc. injectez-le dans le constructeur
constructor(
@Inject(Window) private _window: Window,
)
Avant la déclaration @Component, vous pouvez le faire aussi,
declare var window: any;
Le compilateur vous permettra en fait d'accéder à la variable de fenêtre globale maintenant puisque vous la déclarez comme une variable globale supposée de type any.
Cependant, je ne suggérerais pas d'accéder à la fenêtre partout dans votre application, vous devriez créer des services qui accèdent / modifient les attributs de fenêtre nécessaires (et injectent ces services dans vos composants) pour définir ce que vous pouvez faire avec la fenêtre sans les laisser modifier le objet de la fenêtre entière.
J'ai utilisé OpaqueToken pour la chaîne '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: [] }),
];
Et utilisé juste pour importer WINDOW_PROVIDERS
en bootstrap dans Angular 2.0.0-rc-4.
Mais avec la sortie d'Angular 2.0.0-rc.5, je dois créer un module séparé:
import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';
@NgModule({
providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }
et juste défini dans la propriété importations de ma 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 {}
À partir d'aujourd'hui (avril 2016), le code de la solution précédente ne fonctionne pas, je pense qu'il est possible d'injecter une fenêtre directement dans App.ts, puis de rassembler les valeurs dont vous avez besoin dans un service pour un accès global dans l'application, mais si vous préférez créer et injecter votre propre service, une solution plus simple est la suivante.
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 introduit InjectToken et crée également un jeton pour le document appelé DOCUMENT . Je pense que c'est la solution officielle et cela fonctionne dans AoT.
J'utilise la même logique pour créer une petite bibliothèque appelée ngx-window-token pour éviter de faire cela encore et encore.
Je l'ai utilisé dans un autre projet et construit dans AoT sans problèmes.
Voici comment je l'ai utilisé dans un autre package
Voici le plunker
Dans votre module
imports: [ BrowserModule, WindowTokenModule ]
Dans votre composant
constructor(@Inject(WINDOW) _window) { }
Voici une autre solution que j'ai proposée récemment après avoir été fatigué d'obtenir defaultView
du DOCUMENT
jeton intégré et de le vérifier pour 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: any
et utilisez-le comme le jeton d'injection DOCUMENT fourni par Angular?
Il suffit de faire
export class AppWindow extends Window {}
et fait
{ provide: 'AppWindow', useValue: window }
pour rendre AOT heureux
Il est possible d'accéder directement à l'objet de la fenêtre via le document
document.defaultView == window
Je sais que la question est de savoir comment injecter l'objet window dans un composant, mais vous faites cela juste pour accéder à localStorage, semble-t-il. Si vous voulez vraiment juste localStorage, pourquoi ne pas utiliser un service qui expose exactement cela, comme h5webstorage . Ensuite, votre composant décrira ses véritables dépendances, ce qui rend votre code plus lisible.
C'est la réponse la plus courte / la plus propre que j'ai trouvée en travaillant avec Angular 4 AOT
Source: 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);
}
}
Vous pouvez utiliser NgZone sur Angular 4:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
C'est également une bonne idée de marquer le DOCUMENT
comme facultatif. Selon les documents Angular:
Le document peut ne pas être disponible dans le contexte d'application lorsque les contextes d'application et de rendu ne sont pas identiques (par exemple, lors de l'exécution de l'application dans un Web Worker).
Voici un exemple d'utilisation de DOCUMENT
pour voir si le navigateur prend en charge 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 merci pour ngx-window-token . Je faisais quelque chose de similaire mais je suis passé au vôtre. C'est mon service pour écouter les événements de redimensionnement de fenêtre et notifier les abonnés.
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});
}
Court et doux et fonctionne comme un charme.
Obtenir un objet fenêtre via DI (Dependency Injection) n'est pas une bonne idée lorsque les variables globales sont accessibles dans toute l'application.
Mais si vous ne souhaitez pas utiliser d'objet window, vous pouvez également utiliser un self
mot-clé qui pointe également vers l'objet window.
Restez simple, les amis!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
En fait, il est très simple d'accéder à un objet de fenêtre, voici mon composant de base et je l'ai testé
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!
. Cependant, sa suppression a résolu le problème pour moi. Utiliser uniquement les 2 premières lignes globales me suffisait.