Angular2 - Как добавить окно в службу angular2

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

24 ответа

В настоящее время это работает для меня (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: Обновлено для угловых 2.1.2 edit3: Добавлено примечание AoT edit4: Добавление any typearound note edit5: обновленное решение для использования WindowRefService, которое исправляет ошибку, полученную при использовании предыдущего решения с другой сборкой edit6: добавление примера пользовательской типизации Window

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

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

}

С выпуском углового 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

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

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

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

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

Вот еще одно решение, которое я недавно придумал после того, как устал получать 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;
        }
    });

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

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

declare var window: any;

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

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

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

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

Затем в вашем сервисе и т. Д. Вставьте его в конструктор

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

Я использовал 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: [] }),
];

И используется только для импорта 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 {}

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

export class AppWindow extends Window {} 

и делать

{ provide: 'AppWindow', useValue: window } 

сделать АОТ счастливым

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

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

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

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

Вот плункер

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

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

constructor(@Inject(WINDOW) _window) { }

На сегодняшний день (апрель 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;
    }
}

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

document.defaultView == window

Это самый короткий / чистый ответ, который я нашел, работая с Angular 4 AOT

Источник: https://github.com/angular/angular/issues/12631

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

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

Это также хорошая идея, чтобы отметить 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
);

Объявите провайдера в вашем модуле:

        providers: [
    { provide: Window, useValue: window }
  ]

В компонент или службу внедрите:

      constructor(private window: Window)

Вы можете получить доступ к свойствам:

      console.log(this.window.document);

Или создайте новые значения:

      (<any>this.window).MyNewValue = 'John Doe' ;

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

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

constructor(private zone: NgZone) {}

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

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

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

}

Если вам нужно внедрить окно, потому что вам действительно нужны свойства, принадлежащие «окну», просто создайте службу, как показано ниже.

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

@Injectable({
  providedIn: 'root',
})
export class WindowService {
  public getInstance(): Window {
    return window;
  }
}

Вот пример, когда становится легко издеваться WindowServiceдля целей тестирования:

      export class AnotherService {
  constructor(private windowService: WindowService) {}

  public hasPreviousPage(): boolean {
    return this.windowService.getInstance().history.length > 2;
  }
}

Однако, если вы используете окно, чтобы затем получить глобально определенную переменную, я бы рекомендовал сначала использовать вместо этого globalThis . Затем набрать глобальный текст можно с помощью модуля машинописного текста , короче говоря: сделайте что-то вроде этого:

Объявите этот окружающий контекст здесь где-нибудь

      declare global {
  function hello():string;
}

Тогда ts не будет жаловаться на следующий код:

      globalThis.hello(); // best way
window.hello(); // okay but window could be undefined in tests, workers or headless nodejs

Обратите внимание, что вам все еще нужно добавить реализацию hello()глобально куда-то.

Конечно, вы также можете (но я НЕ РЕКОМЕНДУЮ) использовать следующий подвох:

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

interface ExtraParams {
hello:() => string;
}

@Injectable({
  providedIn: 'root',
})
export class WindowService {
  public getInstance(): Window & ExtraParams {
    return window as unknown as Window & ExtraParams;
  }
}

@maxisam спасибо за маркер окна ngx. Я делал что-то подобное, но переключился на ваш. Это мой сервис для прослушивания событий изменения размера окна и уведомления подписчиков.

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

Короткая и сладкая и работает как шарм.

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

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

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

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

<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
Другие вопросы по тегам