ウィンドウをサービスに挿入する方法は?


110

私はTypeScriptでAngular 2サービスを書いていますlocalstoragewindowAngular 1.xのようなグローバル変数を参照したくないので、ブラウザーオブジェクトへの参照をサービスに挿入します$window

それ、どうやったら出来るの?

回答:


134

これは現在私のために機能しています(2018-03、AoTを使用した角度5.2、angular-cliおよびカスタムWebpackビルドでテスト済み):

まず、ウィンドウへの参照を提供する注入可能なサービスを作成します。

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

次に、そのサービスをルートAppModuleに登録して、どこにでも注入できるようにします。

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

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

そして、後で注入する必要がある場所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;
    }
...

nativeDocumentアプリケーションでこれらを使用する場合、同様の方法でこのサービスに他のグローバルを追加することもできます。


編集:Truchainzの提案で更新されました。edit2:角度2.1.2の更新edit3:AoTノートの追加edit4:anyタイプ回避策ノートの追加edit5:別のビルドで以前のソリューションを使用したときに発生していたエラーを修正するWindowRefServiceを編集edit6:カスタムウィンドウタイプの例を追加


1
@Injectをコンストラクタパラメータに含めると、などのエラーがたくさん発生しましたORIGINAL EXCEPTION: No provider for Window!。ただし、それを削除すると問題が解決しました。最初の2つのグローバル回線を使用するだけで十分でした。
TrieuNomad 2016年

なし-面白い^^私はカップルよりデモのプロジェクトでそれを試す必要があります@Inject、私はなっていたNo provider for Windowエラーを。マニュアルがいらないのはいいですね@Inject
elwyn 2016年

2.1.2では@Inject(Window)、これを機能させるために使用しなければなりませんでした
James Kleeh

1
angular.io/docs/ts/latest/guide/…。ああ、よく読まなかった
Teedeez、

2
@ブライアンはい、まだアクセスwindowしていますが、その間のサービスを使用するwindowと、単体テストでネイティブスタッフをスタブアウトできます。SSRについて言及したように、サーバーのモック/ヌープウィンドウを公開する代替サービスを提供できます。私がAOTについて言及した理由は、Angularが更新されたときに、ラッピングウィンドウの初期のソリューションのいくつかがAOTで壊れたためです。
elwyn

34

angular 2.0.0-rc.5のリリースに伴い、NgModuleが導入されました。以前の解決策が機能しなくなりました。これは私がそれを修正するためにしたことです:

app.module.ts:

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

一部のコンポーネントでは:

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

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

文字列「Window」の代わりにOpaqueTokenを使用することもできます

編集:

AppModuleは、次のようにmain.tsでアプリケーションをブートストラップするために使用されます。

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

platformBrowserDynamic().bootstrapModule(AppModule)

NgModuleの詳細については、Angular 2のドキュメントをご覧くださいhttps ://angular.io/docs/ts/latest/guide/ngmodule.html


19

プロバイダーを設定した後、それを注入することができます:

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

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

しかしwindow.var、ページを変更してもページのコンテンツは変わりません
Ravinder Payal

6
ウィンドウはインジェクタブルではないため、これはSafariでは機能しませんでした。必要なWindowのプロパティを含む独自のInjectable型を作成する必要がありました。より良いアプローチは、他の回答に記載されているようにサービスを作成することでした
daveb

この方法は機能しません。useValueは、ユーザーが指定した値のコピーを実際に作成するためです。github.com/angular/angular/issues/10788#issuecomment-300614425を参照してください。この方法は、useFactoryを使用するように変更し、コールバックから値を返す場合に機能します。
Levi Lindsey

18

注入されたドキュメントからウィンドウを取得できます。

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

Angular 2.1.1で動作させるには@Inject、文字列を使用してウィンドウ処理する必要がありました

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

そして、このようにそれを模擬

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

そして、普通に@NgModule私はこのようにそれを提供します

{ provide: 'Window', useValue: window }

10

Angular RC4では、上記の回答のいくつかを組み合わせた次の作品があり、ルートapp.tsにプロバイダーを追加します:

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

次に、サービスなどでコンストラクタに注入します

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

10

@Component宣言の前に、それを行うこともできます。

declare var window: any;

コンパイラーは、any型の想定グローバル変数として宣言しているため、実際にグローバルウィンドウ変数にアクセスできるようになります。

ただし、アプリケーションのすべての場所でウィンドウにアクセスすることはお勧めしません。必要なウィンドウ属性にアクセス/変更するサービスを作成(およびコンポーネントにそれらのサービスを挿入)して、ウィンドウを変更せずにウィンドウで実行できることをスコープする必要があります。ウィンドウオブジェクト全体。


サーバー側でレンダリングを行うと、コードが壊れます.srver側ではウィンドウオブジェクトがなく、独自のウィンドウオブジェクトを挿入する必要があるためです。
Alex Nikulin

9

「ウィンドウ」文字列にOpaqueTokenを使用しました

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: [] }),
];

そしてWINDOW_PROVIDERS、Angular 2.0.0-rc-4のブートストラップにインポートするためだけに使用されます。

しかし、Angular 2.0.0-rc.5のリリースでは、別のモジュールを作成する必要があります。

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

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

そしてちょうど私のメインのインポートプロパティで定義されています 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

今日(2016年4月)の時点では、前のソリューションのコードは機能していません。ウィンドウをApp.tsに直接挿入し、必要な値をサービスに収集して、アプリケーションのグローバルアクセスに使用できると思いますが、独自のサービスを作成して注入したい場合、より簡単な解決策はこれです。

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ではInjectTokenが導入されており、ドキュメントのトークンとしてDOCUMENTも作成されます。これは公式のソリューションであり、AoTで機能すると思います。

同じロジックを使用して、ngx-window-tokenと呼ばれる小さなライブラリを作成し、これが何度も繰り返されるのを防ぎます

私はそれを他のプロジェクトで使用し、問題なくAoTでビルドしました。

これは他のパッケージでどのように使用したかです

これがプランカーです

あなたのモジュールで

imports: [ BrowserModule, WindowTokenModule ] コンポーネント内

constructor(@Inject(WINDOW) _window) { }



4

ドキュメントを通してウィンドウのオブジェクトに直接アクセスする機会があります

document.defaultView == window

4

これはdefaultViewDOCUMENT組み込みトークンから取得して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
それで、これをプロバイダーフォルダー(たとえば)に入れ、コンポーネントのコンストラクターでこの注入トークンを使用しますか?@Inject(WINDOW) private _window: anyAngularが提供するDOCUMENTインジェクショントークンのように使用しますか?
Sparker73

はい、それだけです。
waterplea

うん。これは完璧に機能します。このシンプルなソリューションの戦車です。
Sparker73

3

私は質問がウィンドウオブジェクトをコンポーネントに注入する方法であることを知っていますが、localStorageに到達するためにこれを行っているようです。本当にlocalStorageだけが必要な場合は、それだけを公開するサービス(h5webstorageなど)を使用しないでください。次に、コンポーネントは実際の依存関係を記述し、コードを読みやすくします。


2
このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。
すべての労働者は必須

3

これは私がAngular 4 AOTで作業していることがわかった最も短い/最もきれいな答えです

出典: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

Angular 4でNgZoneを使用できます。

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

constructor(private zone: NgZone) {}

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

2

DOCUMENTをオプションとしてマークすることもお勧めします。Angularのドキュメントによると:

アプリケーションコンテキストとレンダリングコンテキストが異なる場合(アプリケーションをWebワーカーで実行している場合など)、ドキュメントはアプリケーションコンテキストで使用できない場合があります。

を使用しDOCUMENTて、ブラウザが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 ngx-window-tokenに感謝します。私は似たようなことをしていましたが、あなたのものに切り替えました。これは、ウィンドウサイズ変更イベントをリッスンし、サブスクライバーに通知するための私のサービスです。

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

短くて甘い、そして魅力のように機能します。


0

アプリケーション全体からグローバル変数にアクセスできる場合、DI(Dependency Injection)を介してウィンドウオブジェクトを取得することはお勧めできません。

ただし、ウィンドウオブジェクトを使用しない場合はself、ウィンドウオブジェクトを指すキーワードも使用できます。


3
それは良いアドバイスではありません。Dependency Injectionを使用すると、クラス(コンポーネント、ディレクティブ、サービス、パイプなど)を(たとえば、ブラウザーがなくても)テストしやすくなり、サーバー側のレンダリングやWebワーカーなどのさまざまなプラットフォームで再利用しやすくなります。それはいくつかのために働くかもしれません、そして単純さはいくつかの魅力があるかもしれませんが、DIを使用することを思いとどまらせることは私見で悪い答えです。
ギュンターZöchbauer

サーバー側でレンダリングを行うと、コードが壊れます.srver側ではウィンドウオブジェクトがなく、独自のウィンドウオブジェクトを挿入する必要があるためです。
Alex Nikulin

-1

シンプルにしてください。

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

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

サーバー側でレンダリングを行うと、コードが壊れます.srver側ではウィンドウオブジェクトがなく、独自のウィンドウオブジェクトを挿入する必要があるためです。
Alex Nikulin

-2

実際、ここでウィンドウオブジェクトにアクセスするのは非常に簡単で、これが私の基本的なコンポーネントであり、動作をテストしました

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

}

サーバー側でレンダリングを行うと、コードが壊れます.srver側ではウィンドウオブジェクトがなく、独自のウィンドウオブジェクトを挿入する必要があるためです。
Alex Nikulin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.