Angular: Провайдер объекта Window
Куда делась моя ссылка $window? Нет проблем, используйте этот провайдер для внедрения window в ваш Angular компонент.
Почему?
Потому что мы не хотим ссылаться на глобальный объект Window прямо в компоненте Angular, так как наше приложение может быть использовано в Angular Universal, и считается лучшей практикой избегать прямой ссылки на глобальные объекты. Пока вы возможно не планируете использовать Angular Universal, это позволяет нам прямо внедрить родной объект браузера Window в компонент.
Чем это отличается?
В то время как существует уже множество решений, которые предлагают методы косвенного доступа к объекту Window, я обнаружил, что многие из них в действительности не выполняют какого-либо условия проверки, которое приложение выполняет в контексте браузера, где у нас будет объект Window.
Вот пример решения, который мне показался неподходящим:
import { Injectable } from '@angular/core';
function getWindow (): any {
return window;
}
@Injectable()
export class WindowRefService {
get nativeWindow (): any {
return getWindow();
}
}
Беглый взгляд на него указывает на то, что проверка не осуществляется, если приложение выполняется в контексте браузера. Далее, мы возможно захотим поддерживать контекст для тестов, где нам может быть понадобится имитировать некоторые свойства или методы в объекте Window.
Предлагаемое решение
Ниже приводится решение создания расширяемого метода для внедрения Window в компоненты Angular. В моем приложении я поместил это в src/app/core/services/window.service.ts:
import { isPlatformBrowser } from "@angular/common";
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core';
/* Create a new injection token for injecting the window into a component. */
export const WINDOW = new InjectionToken('WindowToken');
/* Define abstract class for obtaining reference to the global window object. */
export abstract class WindowRef {
get nativeWindow(): Window | Object {
throw new Error('Not implemented.');
}
}
/* Define class that implements the abstract class and returns the native window object. */
export class BrowserWindowRef extends WindowRef {
constructor() {
super();
}
get nativeWindow(): Window | Object {
return window;
}
}
/* Create an factory function that returns the native window object. */
export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: Object): Window | Object {
if (isPlatformBrowser(platformId)) {
return browserWindowRef.nativeWindow;
}
return new Object();
}
/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */
const browserWindowProvider: ClassProvider = {
provide: WindowRef,
useClass: BrowserWindowRef
};
/* Create an injectable provider that uses the windowFactory function for returning the native window object. */
const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: windowFactory,
deps: [ WindowRef, PLATFORM_ID ]
};
/* Create an array of providers. */
export const WINDOW_PROVIDERS = [
browserWindowProvider,
windowProvider
];
Несколько вещей, на которые стоит обратить внимание:
- У меня есть InjectionToken объявленный как WINDOW , который экспортируется. Мы будем использовать это для внедрения объекта Window в наши компоненты.
- Я определяю абстрактный класс WindowRef который имеет аксессор для свойства nativeWindow.
- Затем, я определяю класс BrowserWindowRef расширяющий абстрактный класс, и реализовывающий свойство nativeWindow возвращать глобально доступный объект Window.
- Функция windowFactory()определяет, выполняется ли приложение в контексте браузера, используя функцию isPlatformBrowser(). Сейчас, если приложение не выполняется в контексте браузера, я просто возвращаю новый Object. Эта часть расширяемая тем, что вы могли бы вернуть имитируемый объект в другой контекст. Функция ожидает экземпляр(инстанс) BrowserWindowRef и объект platformId. Эти зависимости определяются в windowProvider.
- browserWindowProvider является (классом провайдера) ClassProvider который предоставляет экземпляр BrowserWindowRef используя injection token (токен внедрения) WindowRef.
- windowProvider является (провайдером-фабрикой) FactoryProvider которая использует windowFactory чтобы возвращать объект Window (или пустой объект) когда используется injection token WINDOW.
- И последнее, объявляем массив провайдеров с именем WINDOW_PROVIDERS.
Чтобы это использовать, необходимо добавить WINDOW_PROVIDERS в массив providers модуля декоратора app.module.ts:
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule
],
providers: [WINDOW_PROVIDERS],
bootstrap: [AppComponent]
})
export class AppModule { }
У вас также может быть CoreModule (возможно с другим именем) – это модуль который содержит все сервисы, интерсепторы, модели и пр., доступные для приложения. В случае необходимости просто импортируйте WINDOW_PROVIDERS в этот модуль, который затем импортируется в AppModule.
После этого, мы можем использовать injection token WINDOW для внедрения window в компонент:
import { WINDOW } from "../core/services/window.service";
export class IndexComponent {
constructor(@Inject(WINDOW) private window: Window) {
console.log(window);
}
}
Пример, который отображает строку агента пользователя window.navigator.userAgent