Bagaimana cara menyuntikkan jendela ke dalam layanan?


111

Saya sedang menulis layanan Angular 2 di TypeScript yang akan menggunakan localstorage. Saya ingin memasukkan referensi ke browserwindow objek ke dalam pelayanan saya karena saya tidak ingin referensi variabel global seperti 1.x sudut $window.

Bagaimana aku melakukan itu?

Jawaban:


135

Ini berfungsi untuk saya saat ini (2018-03, angular 5.2 dengan AoT, diuji dalam angular-cli dan build webpack kustom):

Pertama, buat layanan injeksi yang menyediakan referensi ke jendela:

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

Sekarang, daftarkan layanan itu dengan AppModule root Anda sehingga dapat disuntikkan di mana saja:

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

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

dan kemudian di mana Anda perlu menyuntikkan 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;
    }
...

Anda mungkin juga ingin menambahkan nativeDocumentdan layanan global lainnya ke layanan ini dengan cara yang sama jika Anda menggunakannya dalam aplikasi Anda.


edit: Diperbarui dengan saran Truchainz. edit2: Diperbarui untuk sudut 2.1.2 edit3: Menambahkan catatan AoT edit4: Menambahkan anyjenis solusi catatan edit5: Solusi yang diperbarui untuk menggunakan WindowRefService yang memperbaiki kesalahan yang saya dapatkan saat menggunakan solusi sebelumnya dengan edit versi yang berbeda6: menambahkan contoh pengetikan Jendela khusus


1
Memiliki @Inject dalam parameter konstruktor menimbulkan banyak kesalahan bagi saya seperti ORIGINAL EXCEPTION: No provider for Window!. Namun, menghapusnya memperbaiki masalah bagi saya. Hanya menggunakan 2 baris global pertama sudah cukup bagi saya.
TrieuNomad

Menarik ^^ Aku harus mencobanya di beberapa proyek yang lebih demo - tanpa @Injectsaya mendapatkan No provider for Windowkesalahan. Cukup bagus tidak membutuhkan manual @Inject!
elwyn

Pada 2.1.2 saya harus menggunakan @Inject(Window)ini untuk bekerja
James Kleeh

1
angular.io/docs/ts/latest/guide/… . Ya ampun, tidak membaca dengan seksama
Teedeez

2
@ Brian ya, itu masih mengakses window, tetapi dengan layanan di antaranya memungkinkan mematikan windowbarang asli dalam pengujian unit, dan saat Anda menyebutkan untuk SSR, layanan alternatif dapat disediakan yang memperlihatkan jendela tiruan / noop untuk server. Alasan saya menyebutkan AOT adalah beberapa solusi awal untuk membungkus jendela rusak di AOT ketika Angular diperbarui.
elwyn

34

Dengan rilis NgModule sudut 2.0.0-rc.5 diperkenalkan. Solusi sebelumnya berhenti berfungsi untuk saya. Inilah yang saya lakukan untuk memperbaikinya:

app.module.ts:

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

Di beberapa komponen:

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

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

Anda juga bisa menggunakan OpaqueToken alih-alih string 'Window'

Edit:

AppModule digunakan untuk mem-bootstrap aplikasi Anda di main.ts seperti ini:

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

platformBrowserDynamic().bootstrapModule(AppModule)

Untuk informasi lebih lanjut tentang NgModule, baca dokumentasi Angular 2: https://angular.io/docs/ts/latest/guide/ngmodule.html


19

Anda bisa menyuntikkannya setelah Anda mengatur penyedia:

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

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

tetapi ketika saya mengubah window.varkonten halaman tidak berubah
Ravinder Payal

6
Ini tidak berfungsi di Safari karena Window tidak dapat disuntikkan. Saya harus membuat tipe Injectable saya sendiri yang berisi properti Window yang saya butuhkan. Pendekatan yang lebih baik mungkin adalah dengan membuat layanan seperti yang dijelaskan dalam jawaban lain
daveb

Pendekatan ini tidak berfungsi, karena useValue sebenarnya membuat salinan dari nilai yang Anda berikan untuknya. Lihat: github.com/angular/angular/issues/10788#issuecomment-300614425 . Pendekatan ini akan berfungsi jika Anda mengubahnya menjadi useFactory dan mengembalikan nilai dari callback.
Levi Lindsey

18

Anda bisa mendapatkan jendela dari dokumen yang diinjeksi.

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

Untuk membuatnya bekerja pada Angular 2.1.1 saya harus melakukan @Injectwindow menggunakan string

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

dan kemudian mengejeknya seperti ini

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

dan secara biasa @NgModulesaya memberikannya seperti ini

{ provide: 'Window', useValue: window }

10

Di Angular RC4, karya-karya berikut adalah kombinasi dari beberapa jawaban di atas, di app.ts root Anda menambahkan penyedia:

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

Kemudian dalam layanan Anda, dll, masukkan ke konstruktor

constructor(
      @Inject(Window) private _window: Window,
)

10

Sebelum deklarasi @Component, Anda juga dapat melakukannya,

declare var window: any;

Kompiler sebenarnya akan membiarkan Anda mengakses variabel global window sekarang karena Anda mendeklarasikannya sebagai variabel global yang diasumsikan dengan tipe any.

Saya tidak akan menyarankan untuk mengakses jendela di mana pun di aplikasi Anda, Anda harus membuat layanan yang mengakses / memodifikasi atribut jendela yang diperlukan (dan menyuntikkan layanan tersebut ke dalam komponen Anda) untuk mengetahui apa yang dapat Anda lakukan dengan jendela tersebut tanpa membiarkan mereka mengubah seluruh objek jendela.


Jika Anda melakukan rendering di sisi server, kode Anda akan rusak karena di sisi srver Anda tidak memiliki objek jendela apa pun, dan Anda perlu menginjeksi milik Anda sendiri.
Alex Nikulin

9

Saya menggunakan OpaqueToken untuk string '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: [] }),
];

Dan digunakan hanya untuk mengimpor WINDOW_PROVIDERS dalam bootstrap di Angular 2.0.0-rc-4.

Tetapi dengan rilis Angular 2.0.0-rc.5 saya perlu membuat modul terpisah:

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

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

dan baru saja ditentukan di properti impor utama saya 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

Mulai hari ini (April 2016), kode dalam solusi sebelumnya tidak berfungsi, saya pikir dimungkinkan untuk menyuntikkan jendela langsung ke App.ts dan kemudian mengumpulkan nilai yang Anda butuhkan ke layanan untuk akses global di Aplikasi, tetapi jika Anda lebih suka membuat dan memasukkan layanan Anda sendiri, solusi cara yang lebih sederhana adalah ini.

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 memperkenalkan InjectToken, dan mereka juga membuat token untuk dokumen yang disebut DOKUMEN . Saya pikir ini adalah solusi resmi dan berfungsi di AoT.

Saya menggunakan logika yang sama untuk membuat perpustakaan kecil yang disebut ngx-window-token untuk mencegah melakukan ini berulang kali.

Saya telah menggunakannya di proyek lain dan membangun AoT tanpa masalah.

Berikut adalah cara saya menggunakannya di paket lain

Ini plunkernya

Di modul Anda

imports: [ BrowserModule, WindowTokenModule ] Di komponen Anda

constructor(@Inject(WINDOW) _window) { }


5

Inilah solusi lain yang saya temukan baru-baru ini setelah saya bosan mendapatkan defaultViewdari DOCUMENTtoken bawaan dan memeriksanya untuk 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
Jadi, saya meletakkan ini di folder penyedia saya (misalnya) dan kemudian saya menggunakan token injeksi ini di konstruktor komponen saya? @Inject(WINDOW) private _window: anydan menggunakannya seperti token injeksi DOKUMEN yang disediakan oleh Angular?
Sparker73

Ya, itu saja.
waterplea

Ya. Ini bekerja dengan sempurna, tank untuk solusi sederhana ini.
Sparker73

4

Itu cukup untuk dilakukan

export class AppWindow extends Window {} 

dan lakukan

{ provide: 'AppWindow', useValue: window } 

untuk membuat AOT bahagia


4

Ada peluang untuk akses langsung ke objek jendela melalui dokumen

document.defaultView == window

3

Saya tahu pertanyaannya adalah bagaimana menyuntikkan objek jendela ke dalam komponen tetapi Anda melakukan ini hanya untuk sampai ke localStorage tampaknya. Jika Anda benar-benar hanya menginginkan localStorage, mengapa tidak menggunakan layanan yang mengekspos hal itu, seperti h5webstorage . Kemudian komponen Anda akan mendeskripsikan dependensi sebenarnya yang membuat kode Anda lebih mudah dibaca.


2
Meskipun tautan ini mungkin menjawab pertanyaan, lebih baik menyertakan bagian penting dari jawaban di sini dan menyediakan tautan untuk referensi. Jawaban link saja bisa menjadi tidak valid jika halaman tertaut berubah.
Semua Pekerja Penting

3

Ini adalah jawaban terpendek / terbersih yang saya temukan bekerja dengan Angular 4 AOT

Sumber: 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

Anda dapat menggunakan NgZone di Angular 4:

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

constructor(private zone: NgZone) {}

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

2

Sebaiknya tandai DOCUMENTsebagai opsional. Per the Angular docs:

Dokumen mungkin tidak tersedia dalam Konteks Aplikasi jika Konteks Aplikasi dan Rendering tidak sama (misalnya saat menjalankan aplikasi ke Web Worker).

Berikut adalah contoh penggunaan DOCUMENTuntuk melihat apakah browser memiliki dukungan 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 terima kasih untuk ngx-window-token . Saya melakukan hal serupa tetapi beralih ke milik Anda. Ini adalah layanan saya untuk mendengarkan acara mengubah ukuran jendela dan memberi tahu pelanggan.

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

Pendek dan manis dan bekerja seperti pesona.


0

Mendapatkan objek jendela melalui DI (Dependency Injection) bukanlah ide yang baik ketika variabel global dapat diakses di seluruh aplikasi.

Namun jika Anda tidak ingin menggunakan objek jendela maka Anda juga dapat menggunakan selfkata kunci yang juga mengarah ke objek jendela.


3
Itu bukan nasihat yang bagus. Dependency Injection membuat kelas (komponen, arahan, layanan, pipa, ...) lebih mudah untuk diuji (misalnya bahkan tanpa browser) dan lebih mudah untuk digunakan kembali pada platform yang berbeda seperti rendering sisi server atau Web Worker. Ini mungkin berhasil untuk beberapa dan kesederhanaan mungkin memiliki beberapa daya tarik, tetapi mengecilkan hati menggunakan DI adalah IMHO jawaban yang buruk.
Günter Zöchbauer

Jika Anda melakukan rendering di sisi server, kode Anda akan rusak karena di sisi srver Anda tidak memiliki objek jendela apa pun, dan Anda perlu menginjeksi milik Anda sendiri.
Alex Nikulin

-1

Tetap sederhana, kawan!

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

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

Jika Anda melakukan rendering di sisi server, kode Anda akan rusak karena di sisi srver Anda tidak memiliki objek jendela apa pun, dan Anda perlu menginjeksi milik Anda sendiri.
Alex Nikulin

-2

Sebenarnya sangat mudah untuk mengakses objek jendela di sini adalah komponen dasar saya dan saya mengujinya berfungsi

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

}

Jika Anda melakukan rendering di sisi server, kode Anda akan rusak karena di sisi srver Anda tidak memiliki objek jendela apa pun, dan Anda perlu menginjeksi milik Anda sendiri.
Alex Nikulin
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.