Wie füge ich Fenster in einen Dienst ein?

110

Ich schreibe einen Angular 2-Dienst in TypeScript, der davon Gebrauch macht localstorage. Ich möchte einen Verweis auf das Browserobjekt windowin meinen Dienst einfügen, da ich keine globalen Variablen wie Angular 1.x referenzieren möchte $window.

Wie mache ich das?

lokanx
quelle

Antworten:

134

Dies funktioniert derzeit für mich (2018-03, Angular 5.2 mit AoT, getestet in Angular-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 injiziert werden kann:

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

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

und dann später, wo Sie injizieren müssen 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;
    }
...

Möglicherweise möchten nativeDocumentSie diesem Service auch ähnliche globale Elemente hinzufügen , wenn Sie diese in Ihrer Anwendung verwenden.


bearbeiten: Aktualisiert mit Truchainz Vorschlag. edit2: Aktualisiert für Angular 2.1.2 edit3: AoT-Notizen hinzugefügt edit4: Hinzufügen von Problemumgehungsnotizen für Notizen edit5: Die anyLösung wurde aktualisiert, um einen WindowRefService zu verwenden, der einen Fehler behebt, den ich bei der Verwendung einer früheren Lösung mit einem anderen Build erhalten habe. edit6: Hinzufügen einer benutzerdefinierten Fenstertypisierung

Elwyn
quelle
1
Das @Inject in den Konstruktorparametern zu haben, warf eine Reihe von Fehlern für mich auf, wie z ORIGINAL EXCEPTION: No provider for Window!. Das Entfernen hat das Problem jedoch für mich behoben. Es hat mir gereicht, nur die ersten beiden globalen Linien zu verwenden.
TrieuNomad
Interessant ^^ Ich werde es in ein paar weiteren Demo-Projekten versuchen müssen - ohne dass @Injectich No provider for WindowFehler bekam. Das ist ziemlich schön, das Handbuch nicht zu brauchen @Inject!
Elwyn
Am 2.1.2 musste ich damit @Inject(Window)arbeiten
James Kleeh
1
angle.io/docs/ts/latest/guide/… . Oh mein Schlechtes, nicht sorgfältig gelesen
Teedeez
2
@Brian Ja, es wird immer noch zugegriffen window, aber mit dem dazwischen liegenden Dienst können native Inhalte in Komponententests gelöscht werden. windowWie Sie für SSR erwähnen, kann ein alternativer Dienst bereitgestellt werden, der ein Mock / Noop-Fenster für den Server anzeigt. Der Grund, warum ich AOT erwähne, sind einige der frühen Lösungen für das Umbrechen von Fenstern, die bei der Aktualisierung von Angular in AOT kaputt gegangen sind.
Elwyn
34

Mit der Freigabe von Angular 2.0.0-rc.5 wurde NgModule eingeführt. Die vorherige Lösung hat bei mir nicht mehr funktioniert. Folgendes habe ich getan, um das Problem zu beheben:

app.module.ts:

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

In einigen Komponenten:

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 Angular 2-Dokumentation: https://angular.io/docs/ts/latest/guide/ngmodule.html

JNK
quelle
19

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
}
Paul Dutka
quelle
aber wenn ich window.varden Inhalt der Seite ändere, ändert sich nicht
Ravinder Payal
6
Dies hat in Safari nicht funktioniert, da das Fenster nicht injizierbar ist. Ich musste meinen eigenen Injectable-Typ erstellen, der die von mir benötigten Eigenschaften von Window enthielt. Ein besserer Ansatz könnte darin bestanden haben, einen Dienst zu erstellen, wie in den anderen Antworten beschrieben
Dave
Dieser Ansatz funktioniert nicht, da useValue tatsächlich eine Kopie des Werts erstellt, den Sie dafür bereitstellen. Siehe: github.com/angular/angular/issues/10788#issuecomment-300614425 . Dieser Ansatz würde funktionieren, wenn Sie ihn in useFactory ändern und den Wert eines Rückrufs zurückgeben würden.
Levi Lindsey
18

Sie können Fenster aus injiziertem 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);
  }

}
Alex Nikulin
quelle
15

Damit es in Angular 2.1.1 funktioniert, musste ich @Injecteine Zeichenfolge verwenden

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

und dann verspotten Sie es so

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

und im gewöhnlichen biete @NgModuleich es so an

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

In Angular RC4 funktioniert das Folgende, was eine Kombination einiger der oben genannten Antworten ist. Fügen Sie in Ihren Root-Apps die Anbieter hinzu:

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

Dann in Ihren Dienst usw. in den Konstruktor injizieren

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

Vor der @ Component-Deklaration können Sie dies auch tun.

declare var window: any;

Der Compiler lässt Sie dann 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, überall in Ihrer Anwendung auf das Fenster zuzugreifen. Sie sollten Dienste erstellen, die auf die erforderlichen Fensterattribute zugreifen / diese ändern (und diese Dienste in Ihre Komponenten einfügen), um zu erfassen, was Sie mit dem Fenster tun können, ohne dass sie das Fenster ändern können ganzes Fensterobjekt.

S. Galarneau
quelle
Wenn Sie serverseitig rendern, wird Ihr Code beschädigt. Auf der srver-Seite haben Sie kein Fensterobjekt und müssen Ihr eigenes einfügen.
Alex Nikulin
9

Ich habe OpaqueToken für die ' Window' -Zeichenfolge 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 WINDOW_PROVIDERSin 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 gerade in der Import-Eigenschaft meines Haupt 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 {}
Chyngyz
quelle
6

Ab heute (April 2016) funktioniert der Code in der vorherigen Lösung nicht mehr. Ich denke, es ist möglich, Fenster direkt in App.ts einzufügen und dann die Werte, die Sie benötigen, in einem Dienst für den globalen Zugriff in der App zu sammeln Wenn Sie es vorziehen, Ihren eigenen Service zu erstellen und einzufügen, 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;
    }
}
Will de la Vega
quelle
6

Angular 4 führt InjectToken ein und erstellt ein Token für das Dokument DOCUMENT . Ich denke, dies ist die offizielle Lösung und sie funktioniert in AoT.

Ich verwende dieselbe Logik, um eine kleine Bibliothek namens ngx-window-token zu erstellen , um dies immer wieder zu verhindern.

Ich habe es in anderen Projekten verwendet und AoT ohne Probleme eingebaut.

Hier ist, wie ich es in einem anderen Paket verwendet habe

Hier ist der Plunker

In Ihrem Modul

imports: [ BrowserModule, WindowTokenModule ] In Ihrer Komponente

constructor(@Inject(WINDOW) _window) { }

Maxisam
quelle
4

Es besteht die Möglichkeit, über das Dokument direkt auf das Objekt des Fensters zuzugreifen

document.defaultView == window
Vasyl Petrov
quelle
4

Hier ist eine andere Lösung, die ich kürzlich gefunden habe, nachdem ich es satt hatte, defaultViewvon einem DOCUMENTeingebauten Token zu kommen und es auf null zu überprü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;
        }
    });
Wasserplädoyer
quelle
1
Also lege ich dies (zum Beispiel) in meinen Provider-Ordner und verwende dann im Injector meiner Komponente dieses Injection-Token? @Inject(WINDOW) private _window: anyund verwenden Sie es wie das von Angular bereitgestellte DOKUMENT-Injektions-Token?
Sparker73
Ja, das ist alles.
Wasserplädoyer
Jep. Es funktioniert perfekt, Tanks für diese einfache Lösung.
Sparker73
3

Ich weiß, dass die Frage ist, wie das Fensterobjekt in eine Komponente eingefügt wird, aber Sie tun dies nur, um zu localStorage zu gelangen, wie es scheint. Wenn Sie wirklich nur localStorage möchten, können Sie einen Dienst wie h5webstorage verwenden, der genau das verfügbar macht . Anschließend beschreibt Ihre Komponente die tatsächlichen Abhängigkeiten, wodurch Ihr Code besser lesbar wird.

SirDarquan
quelle
2
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert.
Alle Arbeiter sind wesentlich
3

Dies ist die kürzeste / sauberste Antwort, die ich bei der Arbeit 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);
  }
}
nwarp
quelle
3

Es ist genug zu tun

export class AppWindow extends Window {} 

und TU

{ provide: 'AppWindow', useValue: window } 

AOT glücklich machen

Vytautas Pranskunas
quelle
2

Sie können NgZone für Angular 4 verwenden:

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

constructor(private zone: NgZone) {}

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

Es ist auch eine gute Idee, das DOCUMENTals optional zu markieren . Gemäß den Angular-Dokumenten:

Das Dokument ist möglicherweise nicht im Anwendungskontext verfügbar, wenn der Anwendungs- und der Renderkontext nicht identisch sind (z. B. wenn die Anwendung in einem Web Worker ausgeführt wird).

Hier ist 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
);
Ole
quelle
0

@maxisam danke für ngx-window-token . Ich habe etwas Ähnliches gemacht, aber zu dir gewechselt. Dies ist mein Dienst zum Abhören von Ereignissen zur Größenänderung von Fenstern 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 bündig und wirkt wie ein Zauber.

Andrew Alderson
quelle
0

Das Abrufen von Fensterobjekten über DI (Dependency Injection) ist keine gute Idee, wenn auf globale Variablen in der gesamten Anwendung zugegriffen werden kann.

Wenn Sie jedoch kein Fensterobjekt verwenden möchten, können Sie auch ein selfSchlüsselwort verwenden, das auch auf ein Fensterobjekt verweist.

Shivang Gupta
quelle
3
Das ist kein guter Rat. Mit Dependency Injection lassen sich Klassen (Komponenten, Direktiven, Dienste, Pipes usw.) einfacher testen (z. B. auch ohne Browser) und auf verschiedenen Plattformen wie serverseitigem Rendering oder Web Workers leichter wiederverwenden. Es mag für einige funktionieren und die Einfachheit mag einen Reiz haben, aber davon abzuraten, DI zu verwenden, ist meiner Meinung nach eine schlechte Antwort.
Günter Zöchbauer
Wenn Sie serverseitig rendern, wird Ihr Code beschädigt. Auf der srver-Seite haben Sie kein Fensterobjekt und müssen Ihr eigenes einfügen.
Alex Nikulin
-1

Halte es einfach, Leute!

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

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
Geoyws
quelle
Wenn Sie serverseitig rendern, wird Ihr Code beschädigt. Auf der srver-Seite haben Sie kein Fensterobjekt und müssen Ihr eigenes einfügen.
Alex Nikulin
-2

Eigentlich ist der sehr einfache Zugriff auf das Fensterobjekt hier meine Grundkomponente und ich habe getestet, ob es funktioniert

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
quelle
Wenn Sie serverseitig rendern, wird Ihr Code beschädigt. Auf der srver-Seite haben Sie kein Fensterobjekt und müssen Ihr eigenes einfügen.
Alex Nikulin