Как внедрить окно в сервис?

Я пишу сервис Angular 2 на TypeScript, который будет использовать localstorage. Я хочу добавить ссылку на объект браузера window в мой сервис, поскольку не хочу ссылаться на глобальные переменные, такие как Angular 1.x $window.

Как мне это сделать?

Ответ 1

В настоящее время это работает для меня (2018-03, angular 5.2 с AoT, протестировано в angular-cli и пользовательской сборке веб-пакета):

Сначала создайте инъекционный сервис, который предоставляет ссылку на окно:

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 и другие глобальные переменные в эту службу аналогичным образом, если вы используете их в своем приложении.


редактировать: Обновлено с предложением Трухайнца. edit2: обновлено для angular 2.1.2 edit3: добавлено примечание AoT edit4: добавлено обходное примечание any типа edit5: обновлено решение для использования WindowRefService, которое исправляет ошибку, полученную при использовании предыдущего решения с другой сборкой edit6: добавлен пример пользовательской типизации Window

Ответ 2

С выпуском 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) {}
}

Вы также можете использовать OpaqueToken вместо строки "Окно"

Edit:

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

Ответ 3

Вы можете просто ввести его после того, как вы установили поставщика:

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

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

Ответ 4

Чтобы заставить его работать в 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 }

Ответ 5

вот услуга, которую я сделал для вас. 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;
  }
}

Ответ 6

Вы можете получить окно из введенного документа.

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

}

Ответ 7

Я использовал 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 в bootstrap в 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 { }

и только что определен в свойстве import моего основного 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 {}

Ответ 8

В Angular RC4 следующие работы, которые представляют собой комбинацию некоторых из вышеперечисленных ответов, в корневом приложении app.ts добавят его поставщикам:

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

Затем в вашей службе и т.д. вставьте его в конструктор

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

Ответ 9

Перед объявлением @Component вы также можете сделать это,

declare var window: any;

Теперь компилятор позволит вам получить доступ к глобальной переменной окна, поскольку вы объявляете ее как предполагаемую глобальную переменную с типом any.

Я бы не предлагал обращаться к окну повсюду в вашем приложении, но вам следует создавать службы, которые получают доступ/модифицируют необходимые атрибуты окна (и внедряют эти службы в ваши компоненты) в объем, который вы можете делать с окном, не позволяя им для изменения всего объекта окна.

Ответ 10

На сегодняшний день (апрель 2016 г.) код в предыдущем решении не работает, я думаю, что можно внедрить окно непосредственно в 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;
    }
}

Ответ 11

В Angular 4 вводится InjectToken, и они также создают токен для документа с именем DOCUMENT. Я думаю, что это официальное решение, и оно работает в AoT.

Я использую ту же логику, чтобы создать небольшую библиотеку под названием ngx-window-token, чтобы предотвратить это снова и снова.

Я использовал его в другом проекте и встроил AoT без проблем.

Вот как я использовал это в другой упаковке

Вот плункер

В вашем модуле

imports: [ BrowserModule, WindowTokenModule ] в вашем компоненте

constructor(@Inject(WINDOW) _window) { }

Ответ 12

Существует возможность прямого доступа к объекту окна через документ

document.defaultView == window

Ответ 13

Я знаю, что вопрос заключается в том, как вставить объект окна в компонент, но вы делаете это только для того, чтобы добраться до localStorage. Если вы действительно хотите localStorage, почему бы не использовать сервис, который предоставляет именно это, например h5webstorage. Затем ваш компонент будет описывать свои реальные зависимости, которые делают ваш код более удобочитаемым.

Ответ 14

Это самый короткий/самый чистый ответ, который я нашел, работая с 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);
  }
}

Ответ 15

Достаточно сделать

export class AppWindow extends Window {} 

и do

{ provide: 'AppWindow', useValue: window } 

сделать AOT счастливым

Ответ 16

Вы можете использовать NgZone на Angular 4:

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

constructor(private zone: NgZone) {}

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

Ответ 17

Также неплохо пометить DOCUMENT как необязательный. В угловых документах:

Документ может быть недоступен в контексте приложения, когда контексты приложения и рендеринга не совпадают (например, при запуске приложения в 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
);

Ответ 18

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

Ответ 19

Получение объекта окна через DI (Dependency Injection) не очень хорошая идея, когда глобальные переменные доступны во всем приложении.

Но если вы не хотите использовать объект окна, вы также можете использовать ключевое слово self которое также указывает на объект окна.

Ответ 20

Вот еще одно решение, которое я defaultView недавно после того, как мне надоело получать defaultView из встроенного токена DOCUMENT и проверять его на ноль:

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

Ответ 21

Будьте проще, ребята!

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

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

Ответ 22

На самом деле это очень простой для доступа к объекту окна здесь мой основной компонент, и я проверил его работу

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

}