Comment injecter une fenêtre dans un service?

111

J'écris un service Angular 2 dans TypeScript qui utilisera localstorage. Je veux injecter une référence au navigateur windowobjet dans mon service que je ne veux pas faire référence à des variables globales comme 1.x angulaire $window.

Comment je fais ça?

Lokanx
la source

Réponses:

135

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' nativeDocumentautres 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 anynote 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

Elwyn
la source
1
Avoir le @Inject dans les paramètres du constructeur a jeté un tas d'erreurs pour moi comme 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.
TrieuNomad
Intéressant ^^ Je vais devoir l'essayer dans quelques autres projets de démonstration - sans que @Injectje reçoive des No provider for Windowerreurs. C'est plutôt sympa de ne pas avoir besoin du manuel @Inject!
elwyn
Sur 2.1.2, j'ai dû utiliser @Inject(Window)pour que cela fonctionne
James Kleeh
1
angular.io/docs/ts/latest/guide/… . Oh mon mauvais, je n'ai pas lu attentivement
Teedeez
2
@Brian oui, il y a toujours accès 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.
elwyn
34

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

JNK
la source
19

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
}
Paul Dutka
la source
mais quand je change le window.varcontenu de la page ne change pas
Ravinder Payal
6
Cela ne fonctionnait pas dans Safari car Window n'est pas injectable. J'ai dû créer mon propre type Injectable qui contenait les propriétés de Window dont j'avais besoin. Une meilleure approche a peut-être été de créer un service comme décrit dans les autres réponses
Daveb
Cette approche ne fonctionne pas, car useValue crée en fait une copie de la valeur que vous lui fournissez. Voir: github.com/angular/angular/issues/10788#issuecomment-300614425 . Cette approche fonctionnerait si vous la modifiiez pour utiliser useFactory et renvoyer la valeur à partir d'un rappel.
Levi Lindsey
18

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);
  }

}
Alex Nikulin
la source
15

Pour le faire fonctionner sur Angular 2.1.1, j'ai dû @Injectfenê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 @NgModuleje le fournis comme ça

{ provide: 'Window', useValue: window }
Klas Mellbourn
la source
10

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,
)
Joël Davey
la source
10

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.

S.Galarneau
la source
Si vous effectuez un rendu côté serveur, votre code sera cassé, car côté serveur, vous n'avez aucun objet window et vous devez injecter le vôtre.
Alex Nikulin le
9

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_PROVIDERSen 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 {}
Chyngyz
la source
6

À 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;
    }
}
Will de la Vega
la source
6

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) { }

maxisam
la source
5

Voici une autre solution que j'ai proposée récemment après avoir été fatigué d'obtenir defaultViewdu DOCUMENTjeton 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;
        }
    });
waterplea
la source
1
Donc, je mets ceci dans mon dossier de fournisseurs (par exemple), puis j'utilise dans le constructeur de mon composant ce jeton d'injection? @Inject(WINDOW) private _window: anyet utilisez-le comme le jeton d'injection DOCUMENT fourni par Angular?
Sparker73 le
Oui, c'est tout ce qu'il y a à faire.
waterplea le
Ouaip. Cela fonctionne parfaitement, des réservoirs pour cette solution simple.
Sparker73 du
4

Il suffit de faire

export class AppWindow extends Window {} 

et fait

{ provide: 'AppWindow', useValue: window } 

pour rendre AOT heureux

Vytautas Pranskunas
la source
4

Il est possible d'accéder directement à l'objet de la fenêtre via le document

document.defaultView == window
Vasyl Petrov
la source
3

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.

SirDarquan
la source
2
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change.
Tous les travailleurs sont essentiels
3

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);
  }
}
nwarp
la source
2

Vous pouvez utiliser NgZone sur Angular 4:

import { NgZone } from '@angular/core';

constructor(private zone: NgZone) {}

print() {
    this.zone.runOutsideAngular(() => window.print());
}
Léonard Pinto
la source
2

C'est également une bonne idée de marquer le DOCUMENTcomme 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 DOCUMENTpour 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
);
Ole
la source
0

@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.

Andrew Alderson
la source
0

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 selfmot-clé qui pointe également vers l'objet window.

Shivang Gupta
la source
3
Ce n'est pas un bon conseil. L'injection de dépendances rend les classes (composants, directives, services, canaux, ...) plus faciles à tester (par exemple même sans navigateur) et plus faciles à réutiliser sur différentes plates-formes comme le rendu côté serveur ou les Web Workers. Cela pourrait fonctionner pour certains et la simplicité pourrait avoir un certain attrait, mais décourager l'utilisation de DI est à mon humble avis une mauvaise réponse.
Günter Zöchbauer
Si vous effectuez un rendu côté serveur, votre code sera cassé, car côté serveur, vous n'avez aucun objet window et vous devez injecter le vôtre.
Alex Nikulin
-1

Restez simple, les amis!

export class HeroesComponent implements OnInit {
  heroes: Hero[];
  window = window;
}

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
geoyws
la source
Si vous effectuez un rendu côté serveur, votre code sera cassé, car côté serveur, vous n'avez aucun objet window et vous devez injecter le vôtre.
Alex Nikulin le
-2

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 );
  }

}
Vikas Kandari
la source
Si vous effectuez un rendu côté serveur, votre code sera cassé, car côté serveur, vous n'avez aucun objet window et vous devez injecter le vôtre.
Alex Nikulin le