web-dev-qa-db-fra.com

Angular2 - Comment injecter une fenêtre dans un service angular2

J'écris un service Angular2 dans TypeScript qui utilisera localstorage. Et je souhaite injecter une référence à l'objet de fenêtre de navigateur dans mon service, car je ne souhaite référencer aucune variable globale. Comme angulaire 1.x $window. Comment je fais ça?

93
lokanx

Cela fonctionne pour moi actuellement (2018-03, version angulaire 5.2 avec AoT, testée dans angular-cli et construction d'un pack Web personnalisé):

Commencez par créer 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();
    }
}

Enregistrez maintenant ce service avec votre racine AppModule pour pouvoir l’injecter partout:

import { WindowRefService } from './window-ref.service';

@NgModule({        
  providers: [
    WindowRefService 
  ],
  ...
})
export class AppModule {}

et 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 nativeDocument et d'autres éléments globaux à ce service de manière similaire si vous les utilisez dans votre application.


edit: Mis à jour avec la suggestion de Truchainz . edit2: Mis à jour pour les valeurs angulaires 2.1.2 edit3: Ajout de notes AoT edit4: Ajout de any note de contournement type edit5: Solution mise à jour pour utiliser un WindowRefService qui corrige une erreur que je recevais lors de l'utilisation de la solution précédente avec une version différente edit6: ajout d'un exemple de frappe de fenêtre personnalisée

120
elwyn

Avec la sortie de 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 résoudre ce problème:

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 aussi utiliser un OpaqueToken au lieu de la chaîne 'Window'

Modifier:

AppModule est utilisé pour amorcer 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, consultez la documentation Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html

29
JNK

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
}
17
Paul Dutka

voici un service que j'ai créé pour vous . https://Gist.github.com/gdi2290/f8a524cdfb1f54f1a59c }

tu peux soit
import {WINDOW, WINDOW_PROVIDERS} from './window-service';
ou
import {WindowRef, WINDOW_PROVIDERS} from './window-service';

@Component({
  providers: [WINDOW_PROVIDERS]
})
class App {
  constructor(win: WindowRef, @Inject(WINDOW) win2) {
    var $window = win.nativeWindow;
    var $window2 = win2;
  }
}
15
gdi2290

Pour que cela fonctionne avec Angular 2.1.1, je devais @Inject window en utilisant une chaîne

  constructor( @Inject('Window') private window: Window) { }

puis se moquer 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 ceci

{ provide: 'Window', useValue: window }
12
Klas Mellbourn

Dans Angular RC4, les travaux suivants combinant certaines des réponses ci-dessus sont ajoutés à l’application racine, ajoutez les fournisseurs:

@Component({
    templateUrl: 'build/app.html',
    providers: [
        anotherProvider,
        { provide: Window, useValue: window }
    ]
})

Puis dans votre service, etc., l'injecter dans le constructeur

constructor(
      @Inject(Window) private _window: Window,
)
9
Joel Davey

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é uniquement pour importer WINDOW_PROVIDERS dans bootstrap dans Angular 2.0.0-rc-4.

Mais avec la sortie de 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é imports de mon 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 {}
9
Chyngyz

Avant la déclaration @Component, vous pouvez également le faire,

declare var window: any;

En fait, le compilateur vous permettra d’accéder maintenant à la variable de fenêtre globale puisque vous la déclarez comme une variable globale supposée de type any.

Je ne conseillerais cependant pas d’accéder à window partout dans votre application. Vous devez créer des services qui permettent d’accéder/modifier les attributs de fenêtre nécessaires (et d’injecter ces services dans vos composants) afin de déterminer ce que vous pouvez faire avec la fenêtre sans les laisser modifier objet de la fenêtre entière.

8
S.Galarneau

À compter d'aujourd'hui (avril 2016), le code de la solution précédente ne fonctionnait 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 d'accès global dans l'application, mais Si vous préférez créer et injecter votre propre service, voici une solution plus simple.

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

Angular 4 présente InjectToken et crée également un jeton pour le document appelé DOCUMENT . Je pense que c'est la solution officielle et que cela fonctionne en mode AoT.

J'utilise la même logique pour créer une petite bibliothèque appelée ngx-window-token afin d'éviter que cela ne se répète. 

Je l'ai utilisé dans un autre projet et construit en AoT sans problèmes.

Voici comment je l'ai utilisé dans autre paquet

Voici le plunker

Dans votre module 

imports: [ BrowserModule, WindowTokenModule ] In votre composant

constructor(@Inject(WINDOW) _window) { }

5
maxisam

Il existe une possibilité d'accès direct à l'objet de fenêtre à travers le document

document.defaultView == window

Vous pouvez obtenir une fenêtre à partir d'un 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);
  }

}
3
Alex Nikulin

Je sais que la question est de savoir comment injecter l'objet window dans un composant, mais vous le faites simplement pour accéder à localStorage semble-t-il. Si vous ne voulez vraiment que localStorage, pourquoi ne pas utiliser un service qui expose simplement cela, comme h5webstorage . Ensuite, votre composant décrira ses dépendances réelles, ce qui rendra votre code plus lisible.

3
SirDarquan

C’est la réponse la plus courte/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);
  }
}
3
nwarp

Vous pouvez utiliser NgZone sur Angular 4:

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

constructor(private zone: NgZone) {}

print() {
    this.zone.runOutsideAngular(() => window.print());
}
2
Leonardo Pinto

Il suffit de faire 

export class AppWindow extends Window {} 

et fait 

{ provide: 'AppWindow', useValue: window } 

rendre AOT heureux

2

C'est aussi une bonne idée de marquer la DOCUMENT comme facultative. Selon les documents angulaires:

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 la variable DOCUMENT pour savoir si le navigateur prend en charge le format 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
);
1
Ole

Voici une autre solution que j'ai trouvée récemment après que j'en ai eu marre de récupérer defaultView de DOCUMENT 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;
        }
    });
0
pokrishka

@ Maxisam merci pour ngx-window-token . Je faisais quelque chose de similaire mais je suis passé au vôtre. Ceci est mon service pour écouter les événements de redimensionnement de la fenêtre et pour 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.

0
Andrew Alderson

Obtenir un objet window via DI (Dependency Injection) n'est pas une bonne idée lorsque des variables globales sont accessibles dans l'application.

Mais si vous ne voulez pas utiliser l'objet window, vous pouvez également utiliser le mot clé self qui pointe également vers l'objet window.

0
Shivang Gupta

En fait, c’est très simple d’accéder à l’objet window Voici mon composant de base et je l’ai testé son fonctionnement

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

}
0
Vikas Kandari