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