Jak wstrzyknąć okno do usługi?

111

Piszę usługę Angular 2 w TypeScript, z której skorzystam localstorage. Chcę wprowadzić odniesienie do przeglądarki windowobiektu w mojej służbie, ponieważ nie chcę, aby odwołać wszelkie zmienne globalne jak Kątowymi 1.x $window.

W jaki sposób mogę to zrobić?

lokanx
źródło

Odpowiedzi:

135

To działa dla mnie obecnie (2018-03, angular 5.2 with AoT, testowane w angular-cli i niestandardowej kompilacji webpacka):

Najpierw utwórz usługę do wstrzykiwania, która zawiera odniesienie do 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();
    }
}

Teraz zarejestruj tę usługę w swoim głównym module AppModule, aby można ją było wstrzykiwać wszędzie:

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

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

a później, gdzie należy wykonać wstrzyknięcie 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;
    }
...

Możesz także chcieć dodać nativeDocumenti inne globalne do tej usługi w podobny sposób, jeśli używasz ich w swojej aplikacji.


edycja: Zaktualizowano sugestią Truchainza. edit2: Zaktualizowano dla Angular 2.1.2 edit3: Dodano uwagi AoT edit4: Dodanie opisu anyobejścia problemu

elwyn
źródło
1
Posiadanie @Inject w parametrach konstruktora wyrzuciło dla mnie masę błędów takich jak ORIGINAL EXCEPTION: No provider for Window!. Jednak usunięcie go rozwiązało problem. Użycie tylko pierwszych 2 globalnych linii było dla mnie wystarczające.
TrieuNomad
Ciekawe ^^ Będę musiał to wypróbować w jeszcze kilku projektach demonstracyjnych - bez błędów @Injectpojawiały się No provider for Windowbłędy. To całkiem fajne, że nie potrzebujesz instrukcji @Inject!
elwyn
W dniu 2.1.2 musiałem użyć, @Inject(Window)aby to zadziałało
James Kleeh
1
angular.io/docs/ts/latest/guide/… . O mój Boże, nie czytałem uważnie
Teedeez
2
@Brian tak, nadal uzyskuje dostęp window, ale z usługą pomiędzy pozwala na usuwanie natywnych windowrzeczy w testach jednostkowych, a jak wspomniałeś dla SSR, można zapewnić alternatywną usługę, która ujawnia okno pozorowane / noop dla serwera. Powodem, dla którego wspominam o AOT, jest kilka wczesnych rozwiązań do zawijania okna, które zepsuły się w AOT po aktualizacji Angular.
elwyn
34

Wraz z wydaniem angular 2.0.0-rc.5 NgModule został wprowadzony. Poprzednie rozwiązanie przestało dla mnie działać. Oto, co zrobiłem, aby to naprawić:

app.module.ts:

@NgModule({        
  providers: [
    { provide: 'Window',  useValue: window }
  ],
  declarations: [...],
  imports: [...]
})
export class AppModule {}

W jakimś komponencie:

import { Component, Inject } from '@angular/core';

@Component({...})
export class MyComponent {
    constructor (@Inject('Window') window: Window) {}
}

Możesz również użyć OpaqueToken zamiast ciągu „Window”

Edytować:

AppModule służy do ładowania aplikacji w main.ts w następujący sposób:

import { platformBrowserDynamic  } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)

Aby uzyskać więcej informacji o NgModule, przeczytaj dokumentację Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html

JNK
źródło
19

Możesz go po prostu wstrzyknąć po ustawieniu dostawcy:

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

constructor(private window: Window) {
    // this.window
}
Paul Dutka
źródło
ale kiedy zmieniam window.varzawartość strony się nie zmienia
Ravinder Payal
6
To nie zadziałało w Safari, ponieważ Window nie jest wstrzykiwany. Musiałem stworzyć własny typ Injectable, który zawierał właściwości okna, których potrzebowałem. Lepszym podejściem mogło być utworzenie usługi, jak opisano w innych odpowiedziach
daveb
To podejście nie działa, ponieważ useValue faktycznie tworzy kopię wartości, którą dla niej podajesz. Zobacz: github.com/angular/angular/issues/10788#issuecomment-300614425 . Takie podejście zadziałałoby, gdybyś zmienił go tak, aby używał useFactory i zwracał wartość z wywołania zwrotnego.
Levi Lindsey
19

Możesz pobrać okno z wstrzykniętego dokumentu.

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
źródło
15

Aby to działało w Angular 2.1.1, musiałem @Injectotworzyć okno za pomocą ciągu znaków

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

a potem kpić z tego w ten sposób

beforeEach(() => {
  let windowMock: Window = <any>{ };
  TestBed.configureTestingModule({
    providers: [
      ApiUriService,
      { provide: 'Window', useFactory: (() => { return windowMock; }) }
    ]
  });

i zwykle @NgModuletak to zapewniam

{ provide: 'Window', useValue: window }
Klas Mellbourn
źródło
10

W Angular RC4 następujące prace, które są kombinacją niektórych z powyższych odpowiedzi, w aplikacji root. T dodaj dostawców:

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

Następnie w usłudze itp. Wstrzyknij go do konstruktora

constructor(
      @Inject(Window) private _window: Window,
)
Joel Davey
źródło
10

Przed deklaracją @Component też możesz to zrobić,

declare var window: any;

Kompilator faktycznie pozwoli ci teraz uzyskać dostęp do globalnej zmiennej okna, ponieważ zadeklarujesz ją jako zakładaną zmienną globalną o typie any.

Nie sugerowałbym jednak dostępu do okna w każdym miejscu aplikacji. Powinieneś tworzyć usługi, które uzyskują dostęp / modyfikują potrzebne atrybuty okna (i wstrzykują te usługi do swoich komponentów), aby określić zakres tego, co możesz zrobić z oknem, nie pozwalając im modyfikować cały obiekt okna.

S.Galarneau
źródło
Jeśli wykonujesz renderowanie po stronie serwera, kod zostanie uszkodzony, ponieważ po stronie serwera nie masz żadnego obiektu okna i musisz wstrzyknąć własny.
Alex Nikulin
9

Użyłem OpaqueToken dla ciągu znaków „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: [] }),
];

I używane tylko do importowania WINDOW_PROVIDERS w bootstrapie w Angular 2.0.0-rc-4.

Ale wraz z wydaniem Angulara 2.0.0-rc.5 muszę stworzyć osobny moduł:

import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';

@NgModule({
    providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }

i właśnie zdefiniowano we właściwości Import mojego pliku głównego 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
źródło
7

Angular 4 wprowadza InjectToken, a także tworzy token dla dokumentu o nazwie DOCUMENT . Myślę, że jest to oficjalne rozwiązanie i działa w AoT.

Używam tej samej logiki do stworzenia małej biblioteki o nazwie ngx-window-token aby zapobiec robieniu tego w kółko.

Użyłem go w innym projekcie i kompilowałem w AoT bez problemów.

Oto jak użyłem go w innym pakiecie

Tutaj jest plunker

W Twoim module

imports: [ BrowserModule, WindowTokenModule ] W twoim komponencie

constructor(@Inject(WINDOW) _window) { }

maxisam
źródło
6

Na dzień dzisiejszy (kwiecień 2016 r.) Kod w poprzednim rozwiązaniu nie działa, myślę, że można wstrzyknąć okno bezpośrednio do App.ts, a następnie zebrać potrzebne wartości do usługi globalnego dostępu w aplikacji, ale jeśli wolisz tworzyć i wprowadzać własne usługi, jest to o wiele prostsze rozwiązanie.

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
źródło
5

Oto inne rozwiązanie, które wpadłem niedawno po tym, jak zmęczyłem się pobieraniem defaultViewz DOCUMENTwbudowanego tokena i sprawdzaniem, czy jest pusty:

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
źródło
1
Więc umieszczam to w folderze moich dostawców (na przykład), a następnie używam tego tokenu iniekcji w konstruktorze mojego składnika? @Inject(WINDOW) private _window: anyi używać go jak tokena iniekcyjnego DOKUMENTU dostarczonego przez Angular?
Sparker73
Tak, to wszystko.
waterplea
Tak. Sprawdza się doskonale, zbiorniki do tego prostego rozwiązania.
Sparker73
4

To wystarczy

export class AppWindow extends Window {} 

i robić

{ provide: 'AppWindow', useValue: window } 

aby uszczęśliwić AOT

Vytautas Pranskunas
źródło
4

Istnieje możliwość bezpośredniego dostępu do obiektu okna poprzez dokument

document.defaultView == window
Wasyl Pietrow
źródło
3

Wiem, że pytanie brzmi, jak wstrzyknąć obiekt okna do komponentu, ale wydaje się, że robisz to tylko po to, aby dostać się do localStorage. Jeśli naprawdę chcesz tylko localStorage, dlaczego nie skorzystać z usługi, która to ujawnia, na przykład h5webstorage . Następnie komponent opiszesz jego rzeczywiste zależności, dzięki czemu Twój kod będzie bardziej czytelny.

SirDarquan
źródło
2
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
Wszyscy pracownicy są niezbędni
3

To najkrótsza / najczystsza odpowiedź, jaką znalazłem podczas pracy z Angular 4 AOT

Źródło: 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
źródło
2

Możesz użyć NgZone w Angular 4:

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

constructor(private zone: NgZone) {}

print() {
    this.zone.runOutsideAngular(() => window.print());
}
Leonardo Pinto
źródło
2

Dobrym pomysłem jest również oznaczenie tego DOCUMENTjako opcjonalnego. Zgodnie z dokumentacją Angular:

Dokument może nie być dostępny w kontekście aplikacji, gdy kontekst aplikacji i renderowania nie są takie same (np. Podczas uruchamiania aplikacji w module roboczym sieci Web).

Oto przykład użycia, DOCUMENTaby sprawdzić, czy przeglądarka obsługuje 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
źródło
0

@maxisam dzięki za ngx-window-token . Robiłem coś podobnego, ale przeszedłem na twoje. To jest moja usługa do słuchania zdarzeń zmiany rozmiaru okna i powiadamiania subskrybentów.

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

Krótkie i słodkie, działa jak urok.

Andrew Alderson
źródło
0

Pobieranie obiektu okna przez DI (Dependency Injection) nie jest dobrym pomysłem, gdy zmienne globalne są dostępne w całej aplikacji.

Ale jeśli nie chcesz używać obiektu okna, możesz również użyć selfsłowa kluczowego, które również wskazuje na obiekt okna.

Shivang Gupta
źródło
4
To nie jest dobra rada. Dependency Injection sprawia, że ​​klasy (komponenty, dyrektywy, usługi, potoki itp.) Są łatwiejsze do testowania (na przykład nawet bez przeglądarki) i łatwiejsze do ponownego użycia na różnych platformach, takich jak renderowanie po stronie serwera lub procesy sieciowe. Dla niektórych może to działać, a prostota może mieć pewien urok, ale zniechęcanie do korzystania z DI jest złą odpowiedzią IMHO.
Günter Zöchbauer
Jeśli wykonujesz renderowanie po stronie serwera, kod zostanie uszkodzony, ponieważ po stronie serwera nie masz żadnego obiektu okna i musisz wstrzyknąć własny.
Alex Nikulin
-1

Niech to będzie proste, ludzie!

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

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
geoyws
źródło
Jeśli wykonujesz renderowanie po stronie serwera, kod zostanie uszkodzony, ponieważ po stronie serwera nie masz żadnego obiektu okna i musisz wstrzyknąć własny.
Alex Nikulin
-2

Właściwie bardzo łatwo jest uzyskać dostęp do obiektu okna, tutaj jest moim podstawowym komponentem i przetestowałem jego działanie

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
źródło
Jeśli wykonujesz renderowanie po stronie serwera, kod zostanie uszkodzony, ponieważ po stronie serwera nie masz żadnego obiektu okna i musisz wstrzyknąć własny.
Alex Nikulin