Ответы:
Это работает для меня в настоящее время (2018-03, angular 5.2 с AoT, протестировано в angular-cli и настраиваемой сборке веб-пакета):
Сначала создайте инъекционный сервис, который предоставляет ссылку на window:
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
и другие глобальные переменные к этой службе аналогичным образом, если вы используете их в своем приложении.
edit: Обновлено с предложением Truchainz. edit2: Обновлено для angular 2.1.2 edit3: Добавлены примечания AoT edit4: Добавление any
примечания обходного пути edit5: Обновленное решение для использования WindowRefService, которое исправляет ошибку, которую я получал при использовании предыдущего решения с другой сборкой edit6: добавление примера настраиваемого набора окон
@Inject
я получаю No provider for Window
ошибки. Это очень хорошо, что не требует руководства @Inject
!
@Inject(Window)
чтобы это работало
window
, но с промежуточной службой он позволяет заглушать нативный window
материал в модульных тестах, и, как вы упомянули, для SSR может быть предоставлена альтернативная служба, которая предоставляет окно макета / noop для сервера. Причина, по которой я упоминаю AOT, - это несколько ранних решений для оборачивания окна в AOT при обновлении Angular.
С выпуском 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 вместо строки 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 { 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);
}
}
Вы можете просто ввести его после того, как установили провайдера:
import {provide} from 'angular2/core';
bootstrap(..., [provide(Window, {useValue: window})]);
constructor(private window: Window) {
// this.window
}
window.var
содержимое страницы, не меняется
Чтобы заставить его работать на 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 }
В Angular RC4 следующие работы, которые представляют собой комбинацию некоторых из приведенных выше ответов, в вашем корневом приложении app.ts добавьте его в поставщиков:
@Component({
templateUrl: 'build/app.html',
providers: [
anotherProvider,
{ provide: Window, useValue: window }
]
})
Затем в вашем сервисе и т. Д. Введите его в конструктор
constructor(
@Inject(Window) private _window: Window,
)
Перед объявлением @Component это тоже можно сделать,
declare var window: any;
Компилятор фактически позволит вам получить доступ к глобальной переменной окна, поскольку вы объявляете ее как предполагаемую глобальную переменную с типом any.
Я бы не стал предлагать доступ к окну везде в вашем приложении, однако вам следует создать службы, которые обращаются / изменяют необходимые атрибуты окна (и внедряют эти службы в ваши компоненты), чтобы определить, что вы можете делать с окном, не позволяя им изменять объект всего окна.
Я использовал 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 { }
и только что определен в свойстве 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 {}
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;
}
}
Вот еще одно решение, которое я недавно придумал после того, как устал получать defaultView
от DOCUMENT
встроенного токена и проверять его на 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;
}
});
@Inject(WINDOW) private _window: any
и использовать его как токен внедрения DOCUMENT, предоставленный Angular?
Достаточно сделать
export class AppWindow extends Window {}
и делай
{ provide: 'AppWindow', useValue: window }
сделать АОТ счастливым
Есть возможность прямого доступа к объекту окна через документ.
document.defaultView == window
Я знаю, что вопрос в том, как внедрить объект окна в компонент, но вы делаете это, кажется, только для того, чтобы добраться до localStorage. Если вам действительно нужен только localStorage, почему бы не использовать службу, которая предоставляет именно это, например h5webstorage . Затем ваш компонент опишет свои реальные зависимости, что сделает ваш код более читабельным.
Это самый короткий / чистый ответ, который я нашел, работая с 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);
}
}
Вы можете использовать NgZone на Angular 4:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
print() {
this.zone.runOutsideAngular(() => window.print());
}
Также неплохо отметить 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
);
@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});
}
Коротко и мило и работает как шарм.
Получение оконного объекта через DI (внедрение зависимостей) не является хорошей идеей, когда глобальные переменные доступны во всем приложении.
Но если вы не хотите использовать объект окна, вы также можете использовать self
ключевое слово, которое также указывает на объект окна.
Будьте проще, ребята!
export class HeroesComponent implements OnInit {
heroes: Hero[];
window = window;
}
<div>{{window.Object.entries({ foo: 1 }) | json}}</div>
На самом деле очень просто получить доступ к объекту окна, вот мой базовый компонент, и я протестировал его работу
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 );
}
}
ORIGINAL EXCEPTION: No provider for Window!
. Однако его удаление устранило проблему для меня. Мне было достаточно использовать только первые 2 глобальные строки.