Ich schreibe einen Angular2-Dienst in TypeScript, der localstorage verwendet. Und ich möchte einen Verweis auf das Browserfensterobjekt in meinen Dienst einfügen, da ich keine globalen Variablen referenzieren möchte. Wie Winkel 1.x $window
. Wie mache ich das?
Dies funktioniert derzeit für mich (2018-03, Winkel 5.2 mit AoT, getestet in Winkel-cli und einem benutzerdefinierten Webpack-Build):
Erstellen Sie zunächst einen injizierbaren Dienst, der einen Verweis auf das Fenster enthält:
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();
}
}
Registrieren Sie diesen Dienst jetzt bei Ihrem Root-AppModule, damit er überall eingefügt werden kann:
import { WindowRefService } from './window-ref.service';
@NgModule({
providers: [
WindowRefService
],
...
})
export class AppModule {}
und später, wo Sie window
injizieren müssen:
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;
}
...
Auf ähnliche Weise können Sie auch nativeDocument
und andere Globals zu diesem Dienst hinzufügen, wenn Sie diese in Ihrer Anwendung verwenden.
edit: Aktualisiert mit Truchainz-Vorschlag . edit2: Aktualisiert für Winkel 2.1.2 edit3: Hinzugefügte AoT-Hinweise edit4: Hinzufügen eines Workaround-Hinweises vom Typ any
edit5: Lösung wurde aktualisiert, um einen WindowRefService zu verwenden, der einen Fehler behebt, den ich erhalten hatte, wenn die vorherige Lösung mit einem anderen Build verwendet wurde edit6: Hinzufügen von benutzerdefinierten Fenstertypen
Mit dem Release von eckig 2.0.0-rc.5 wurde NgModule eingeführt. Die vorherige Lösung funktionierte nicht mehr für mich. So habe ich das Problem behoben:
app.module.ts:
@NgModule({
providers: [
{ provide: 'Window', useValue: window }
],
declarations: [...],
imports: [...]
})
export class AppModule {}
In einer Komponente:
import { Component, Inject } from '@angular/core';
@Component({...})
export class MyComponent {
constructor (@Inject('Window') window: Window) {}
}
Sie können auch ein OpaqueToken anstelle der Zeichenfolge 'Window' verwenden.
Bearbeiten:
Das AppModule wird verwendet, um Ihre Anwendung in main.ts wie folgt zu booten:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
Weitere Informationen zu NgModule finden Sie in der Dokumentation zu Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html
Sie können es einfach injizieren, nachdem Sie den Anbieter festgelegt haben:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
hier ist ein Service, den ich für Sie gemacht habe . https://Gist.github.com/gdi2290/f8a524cdfb1f54f1a59c
du kannst entwederimport {WINDOW, WINDOW_PROVIDERS} from './window-service';
oderimport {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;
}
}
Damit es mit Angular 2.1.1 funktioniert, musste ich @Inject
-Fenster mit einem String verwenden
constructor( @Inject('Window') private window: Window) { }
und dann verspotten es so
beforeEach(() => {
let windowMock: Window = <any>{ };
TestBed.configureTestingModule({
providers: [
ApiUriService,
{ provide: 'Window', useFactory: (() => { return windowMock; }) }
]
});
und im gewöhnlichen @NgModule
gebe ich es so vor
{ provide: 'Window', useValue: window }
In Angular RC4 funktioniert Folgendes, was eine Kombination einiger der oben genannten Antworten ist, in Ihrem Root-App.ts fügen Sie die Anbieter hinzu:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
Dann in Ihrem Service etc in den Konstruktor einfügen
constructor(
@Inject(Window) private _window: Window,
)
Ich habe OpaqueToken für 'Window'-String verwendet:
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: [] }),
];
Und nur zum Importieren von WINDOW_PROVIDERS
in Bootstrap in Angular 2.0.0-rc-4 verwendet.
Aber mit der Veröffentlichung von Angular 2.0.0-rc.5 muss ich ein separates Modul erstellen:
import { NgModule } from '@angular/core';
import { WINDOW_PROVIDERS } from './window';
@NgModule({
providers: [WINDOW_PROVIDERS]
})
export class WindowModule { }
und nur in der Importeigenschaft meines Hauptcodes definiert 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 {}
Vor der @Component-Deklaration können Sie das auch tun
declare var window: any;
Der Compiler lässt Sie nun tatsächlich auf die globale Fenstervariable zugreifen, da Sie sie als angenommene globale Variable mit dem Typ any deklarieren.
Ich würde jedoch nicht empfehlen, auf das Fenster in Ihrer Anwendung überall zuzugreifen. Sie sollten Dienste erstellen, die auf die erforderlichen Fensterattribute zugreifen bzw. diese ändern (und diese Dienste in Ihre Komponenten einfügen), um die Möglichkeiten zu bestimmen, die Sie mit dem Fenster tun können, ohne dass Sie es ändern müssen gesamtes Fensterobjekt.
Seit heute (April 2016) funktioniert der Code in der vorherigen Lösung nicht. Ich denke, es ist möglich, Fenster direkt in App.ts einzuspeisen und dann die erforderlichen Werte für einen globalen Zugriff in der App zu sammeln Wenn Sie es vorziehen, Ihren eigenen Dienst zu erstellen und einzuspeisen, ist dies eine einfachere Lösung.
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;
}
}
In Angular 4 wird InjectToken eingeführt. Außerdem wird ein Token für das Dokument namens DOCUMENT erstellt. Ich denke, das ist die offizielle Lösung und funktioniert in AoT.
Ich verwende dieselbe Logik, um eine kleine Bibliothek mit dem Namen ngx-window-token zu erstellen, um dies nicht immer wieder zu verhindern.
Ich habe es in einem anderen Projekt verwendet und in AoT ohne Probleme erstellt.
So habe ich es in other package verwendet
Hier ist der Plunker
In deinem Modul
imports: [ BrowserModule, WindowTokenModule ]
.__In Ihrer Komponente
constructor(@Inject(WINDOW) _window) { }
Es besteht die Möglichkeit, über das Dokument direkt auf das Fensterobjekt zuzugreifen
document.defaultView == window
Sie können ein Fenster aus dem eingefügten Dokument erhalten.
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);
}
}
Ich weiß, die Frage ist, wie man das Fensterobjekt in eine Komponente einfügt, aber Sie tun dies nur, um zu localStorage zu gelangen. Wenn Sie wirklich nur localStorage möchten, können Sie einen Dienst verwenden, der genau das bereitstellt, z. B. h5webstorage . Dann wird Ihre Komponente ihre realen Abhängigkeiten beschreiben, wodurch Ihr Code lesbarer wird.
Dies ist die kürzeste/sauberste Antwort, die ich mit Angular 4 AOT gefunden habe
Quelle: 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);
}
}
Sie können NgZone auf Angular 4 verwenden:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
Es ist genug zu tun
export class AppWindow extends Window {}
und TU
{ provide: 'AppWindow', useValue: window }
aOT glücklich zu machen
Es ist auch eine gute Idee, DOCUMENT
als optional zu markieren. Nach den Angular-Dokumenten:
Dokument ist möglicherweise nicht im Anwendungskontext verfügbar, wenn Anwendungs- und Wiedergabekontext nicht identisch sind (z. B. beim Ausführen der Anwendung in einem Web Worker).
Hier ein Beispiel für die Verwendung von DOCUMENT
, um festzustellen, ob der Browser SVG-Unterstützung bietet:
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
);
Hier ist eine andere Lösung, die ich kürzlich gefunden habe, nachdem ich es satt hatte, defaultView
von DOCUMENT
eingebautem Token zu bekommen und es auf null zu prüfen:
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;
}
});
@maxisam Danke für ngx-window-token . Ich machte etwas Ähnliches, wechselte aber zu dir. Dies ist mein Service zum Abhören von Fenstern zur Größenänderung von Ereignissen und zum Benachrichtigen von Abonnenten.
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});
}
Kurz und süß und wirkt wie ein Zauber.
Das Holen eines Fensterobjekts über DI (Dependency Injection) ist keine gute Idee, wenn globale Variablen in der gesamten Anwendung verfügbar sind.
Wenn Sie jedoch kein Fensterobjekt verwenden möchten, können Sie auch das Schlüsselwort self
verwenden, das auch auf das Fensterobjekt verweist.
Eigentlich ist es sehr einfach, auf das Window-Objekt zuzugreifen
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 );
}
}