Come inserire una finestra in un servizio?


111

Sto scrivendo un servizio Angular 2 in TypeScript che utilizzerà localstorage. Voglio iniettare un riferimento al browser windowoggetto in mio servizio dal momento che non voglio fare riferimento a tutte le variabili globali come 1.x angolare $window.

Come lo faccio?

Risposte:


135

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


1
Avere @Inject nei parametri del costruttore ha generato un sacco di errori per me come 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.
TrieuNomad

Interessante ^^ Dovrò provarlo in un altro paio di progetti demo - senza @Injectche stavo ottenendo No provider for Windowerrori. È carino non aver bisogno del manuale @Inject!
elwyn

Il 2.1.2 ho dovuto usare @Inject(Window)perché funzionasse
James Kleeh

1
angular.io/docs/ts/latest/guide/… . Oh mio male, non ho letto attentamente
Teedeez

2
@Brian sì, sta ancora accedendo 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.
elwyn

34

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


19

Puoi semplicemente iniettarlo dopo aver impostato il provider:

import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);

constructor(private window: Window) {
    // this.window
}

ma quando cambio il window.varcontenuto della pagina non cambia
Ravinder Payal

6
Questo non ha funzionato in Safari poiché la finestra non è iniettabile. Ho dovuto creare il mio tipo di Injectable che conteneva le proprietà di Window che avevo richiesto. Un approccio migliore potrebbe essere stato quello di creare un servizio come descritto nelle altre risposte
daveb

Questo approccio non funziona, perché useValue crea effettivamente una copia del valore fornito per esso. Vedi: github.com/angular/angular/issues/10788#issuecomment-300614425 . Questo approccio funzionerebbe se lo cambiassi per usare useFactory e restituisse il valore da una richiamata.
Levi Lindsey

18

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

}

15

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 }

10

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,
)

10

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.


Se esegui il rendering lato server, il codice verrà danneggiato, perché su srver non hai alcun oggetto finestra e devi iniettare il tuo.
Alex Nikulin

9

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

6

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

6

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


5

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

1
Quindi, lo metto nella mia cartella dei provider (ad esempio) e quindi utilizzo nel costruttore del mio componente questo token di iniezione? @Inject(WINDOW) private _window: anye usarlo come il token di iniezione DOCUMENT fornito da Angular?
Sparker73

Sì, è tutto quello che c'è da fare.
waterplea

Sì. Funziona perfettamente, serbatoi per questa semplice soluzione.
Sparker73

4

È abbastanza da fare

export class AppWindow extends Window {} 

e fai

{ provide: 'AppWindow', useValue: window } 

per rendere felice AOT


4

C'è un'opportunità per l'accesso diretto all'oggetto della finestra attraverso il documento

document.defaultView == window

3

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.


2
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere le parti essenziali della risposta qui e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
Tutti i lavoratori sono essenziali

3

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

2

Puoi usare NgZone su Angular 4:

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

constructor(private zone: NgZone) {}

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

2

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

0

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


0

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.


3
Non è un buon consiglio. Dependency Injection rende le classi (componenti, direttive, servizi, pipe, ...) più facili da testare (ad esempio anche senza un browser) e più facili da riutilizzare su diverse piattaforme come il rendering lato server o Web Worker. Potrebbe funzionare per alcuni e la semplicità potrebbe avere qualche fascino, ma IMHO scoraggiare l'uso di DI è una cattiva risposta.
Günter Zöchbauer

Se esegui il rendering lato server, il codice verrà danneggiato, perché su srver non hai alcun oggetto finestra e devi iniettare il tuo.
Alex Nikulin,

-1

Mantienilo semplice, gente!

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

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>

Se esegui il rendering lato server, il codice verrà danneggiato, perché su srver non hai alcun oggetto finestra e devi iniettare il tuo.
Alex Nikulin

-2

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

}

Se esegui il rendering lato server, il codice verrà danneggiato, perché su srver non hai alcun oggetto finestra e devi iniettare il tuo.
Alex Nikulin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.