DevGang
Авторизоваться

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

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить