首页 文章

Angular2 - 如何将窗口注入angular2服务

提问于
浏览
99

我'm writing an Angular2 service in TypeScript that will make use of localstorage. And I want to inject a reference to the browser window object into my service since I don' t想要引用任何全局变量 . 像angular 1.x $window . 我怎么做?

21 回答

  • 30

    你可以在Angular 4上使用NgZone:

    import { NgZone } from '@angular/core';
    
    constructor(private zone: NgZone) {}
    
    print() {
        this.zone.runOutsideAngular(() => window.print());
    }
    
  • 0

    它也是一个将 DOCUMENT 标记为可选的好主意 . 根据Angular文档:

    当应用程序和渲染上下文不相同时(例如,将应用程序运行到Web Worker中时),应用程序上下文中可能没有文档 .

    以下是使用 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
    );
    
  • 9

    这对我来说当前很有用(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 类型解决方法注意编辑5:更新了解决方案以使用WindowRefService修复了我在使用以前的解决方案时使用不同的构建编辑时遇到的错误6:添加示例自定义窗口类型

  • -1

    随着角度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) {}
    }
    

    您也可以使用OpaqueToken而不是字符串'Window'

    编辑:

    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

  • 1

    您可以在设置提供程序后注入它:

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

    这是我为你做的服务 . https://gist.github.com/gdi2290/f8a524cdfb1f54f1a59c

    你也可以
    import {WINDOW, WINDOW_PROVIDERS} from './window-service';
    要么
    import {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;
      }
    }
    
  • 8

    为了使它在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 }
    
  • 6

    我用OpaqueToken为'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: [] }),
    ];
    

    并用于在Angular 2.0.0-rc-4中的bootstrap中导入 WINDOW_PROVIDERS .

    但随着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 {}
    
  • 15

    在Angular RC4中,以下工作是上述一些答案的组合,在您的根应用程序中添加提供程序:

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

    然后在您的服务等中将它注入构造函数

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

    在@Component声明之前,你也可以这样做,

    declare var window: any;
    

    编译器实际上将允许您现在访问全局窗口变量,因为您将其声明为具有类型any的假定全局变量 .

    我建议您不要在应用程序的任何位置访问窗口,您应该创建访问/修改所需窗口属性的服务(并在组件中注入这些服务)以限制您可以对窗口执行的操作,而不必让他们修改整个窗口对象 .

  • 122

    截至今天(2016年4月),上一个解决方案中的代码不起作用,我认为可以将窗口直接注入App.ts,然后将所需的值收集到服务中以便在App中进行全局访问,但是如果您更喜欢创建和注入自己的服务,那么更简单的解决方案就是这样 .

    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;
        }
    }
    
  • 0

    Angular 4引入了InjectToken,它们还为文档创建了一个名为DOCUMENT的令牌 . 我认为这是官方解决方案,它适用于AoT .

    我使用相同的逻辑来创建一个名为ngx-window-token的小型库,以防止一遍又一遍地执行此操作 .

    我已经在其他项目中使用它并在没有问题的情况下在AoT中构建 .

    以下是我在other package中的使用方法

    这是plunker

    在你的模块中

    imports: [ BrowserModule, WindowTokenModule ] 在您的组件中

    constructor(@Inject(WINDOW) _window) { }

  • 9

    有机会通过文档直接访问窗口对象

    document.defaultView == window
    
  • 3

    您可以从注入的文档中获取窗口 .

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

    我知道问题是如何将窗口对象注入一个组件,但你这样做只是为了到达localStorage . 如果你真的只想要localStorage,为什么不使用一个只暴露它的服务,比如h5webstorage . 然后,您的组件将描述其真正的依赖关系,使您的代码更具可读性 .

  • 4

    这是我发现使用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);
      }
    }
    
  • -1

    这足够了

    export class AppWindow extends Window {}
    

    并做

    { provide: 'AppWindow', useValue: window }
    

    让AOT高兴

  • 17

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

    短而甜美,充满魅力 .

  • 3

    在整个应用程序中访问全局变量时,通过DI(依赖注入)获取窗口对象不是一个好主意 .

    但是如果你不想使用window对象,那么你也可以使用 self 关键字,它也指向window对象 .

  • 6

    伙计们,保持简单!

    export class HeroesComponent implements OnInit {
      heroes: Hero[];
      window = window;
    }
    
    <div>{{window.Object.entries({ foo: 1 }) | json}}</div>
    
  • 4

    实际上,访问窗口对象非常简单,这是我的基本组件,我测试了它的工作原理

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

相关问题