如何将窗口注入服务?


110

我正在TypeScript中编写Angular 2服务,该服务将使用localstorage。我想将对浏览器window对象的引用注入到我的服务中,因为我不想引用任何全局变量,例如Angular1.x $window

我怎么做?

Answers:


134

目前这对我有用(2018-03,使用AoT的angular 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:添加了示例自定义Window键入


1
在构造函数参数中使用@Inject对我来说会引发很多错误ORIGINAL EXCEPTION: No provider for Window!。但是,删除它为我解决了这个问题。仅使用前2条全局行就足够了。
TrieuNomad

有趣的^^我将不得不在更多的演示项目中进行尝试-否则@Inject我不会No provider for Window出错。不需要手册真是太好了@Inject
elwyn16年

2.1.2我不得不使用@Inject(Window)这个工作
詹姆斯Kleeh

1
angular.io/docs/ts/latest/guide/...。噢,我的坏,没仔细看
Teedeez

2
@Brian是的,它仍在访问中window,但由于两者之间具有服务,因此可以window在单元测试中存储本机内容,并且正如您提到的SSR一样,可以提供替代服务,该服务公开了服务器的模拟/循环窗口。我提到AOT的原因是当Angular更新时AOT中包装窗口中断的几种早期解决方案。
elwyn

34

随着角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


19

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

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

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

但是,当我更改window.var页面内容时,该内容不会更改
Ravinder Payal's

6
这在Safari中不起作用,因为Window无法注入。我必须创建自己的Injectable类型,其中包含所需的Window属性。更好的方法可能是按照其他答案中的描述创建服务
daveb,2016年

这种方法行不通,因为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的假定全局变量。

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


如果在服务器端进行渲染,则代码将被破坏。因为在服务器端,您没有任何窗口对象,因此需要自己注入。
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 { }

并在我的main的imports属性中定义 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,然后将所需的值收集到服务中以在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;
    }
}

6

Angular 4引入了InjectToken,并且它们还为文档创建了一个称为DOCUMENT的令牌。我认为这是官方解决方案,并且可以在AoT中使用。

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

我在其他项目中使用了它,并在AoT中进行了构建,没有任何问题。

这是我在其他包装中使用它的方式

这是the

在您的模块中

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

constructor(@Inject(WINDOW) _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
因此,我将其放入我的provider文件夹中(例如),然后在组件的构造函数中使用此注入令牌?@Inject(WINDOW) private _window: any并像Angular提供的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);
  }
}

3

这足以做

export class AppWindow extends Window {} 

并做

{ provide: 'AppWindow', useValue: window } 

使AOT开心


2

您可以在Angular 4上使用NgZone:

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

constructor(private zone: NgZone) {}

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

2

将标记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
);

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(依赖注入)获取窗口对象不是一个好主意。

但是,如果您不想使用窗口对象,则也可以使用self也指向窗口对象的关键字。


3
这不是一个好建议。依赖注入使类(组件,指令,服务,管道等)更易于测试(例如,即使没有浏览器),也更易于在服务器端渲染或Web Workers等不同平台上重用。它可能对某些人有用,并且简单性可能会吸引一些人,但是使用DI阻止使用IMHO是个错误的答案。
君特Zöchbauer

如果在服务器端进行渲染,则代码将被破坏。因为在服务器端,您没有任何窗口对象,因此需要自己注入。
Alex Nikulin

-1

保持简单,伙计们!

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

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

如果在服务器端进行渲染,则代码将被破坏。因为在服务器端,您没有任何窗口对象,因此需要自己注入。
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 );
  }

}

如果在服务器端进行渲染,则代码将被破坏。因为在服务器端,您没有任何窗口对象,因此需要自己注入。
Alex Nikulin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.