Как использовать Window DOM в Angular
Как разработчики Angular, мы сталкиваемся с множеством проблем в нашей повседневной разработке. Одной из них является необходимость взаимодействия с JavaScript DOM в приложении Angular, например, с глобальным объектом window. Cчитается хорошей практикой преобразовывать объекты, не связанные с Angular, такие как объект window, внедряемыми зависимостями, а не использовать их напрямую. Разберемся почему.
Фреймворк Angular кроссплатформенный, что означает, что он может работать в различных окружениях, таких как мобильные устройства и серверы. Приложение Angular может быть запущено на сервере с использованием техники Server Side Rendering (SSR). Когда мы используем SSR в Angular, объект window
отсутствует. Он доступен только в среде браузера. Чтобы преодолеть эту проблему и сделать наше приложение платформенно независимым, нам необходимо предоставить объект window
через механизм внедрения зависимостей Angular. Таким образом, мы можем использовать объект window
условно в зависимости от окружения, в котором запущено приложение, и обеспечить его правильную работу на сервере.
Еще одна причина заключается в том, что объект window
может быть недоступен при выполнении модульных тестов в среде непрерывной интеграции (CI). В этом случае мы можем воспользоваться механизмом внедрения зависимостей Angular и предоставить альтернативную реализацию объекта window при выполнении модульных тестов.
В целом, если мы думаем о том что приложение будет расти - лучше сразу заложить правильную архитектуру. Разберемся, как.
Использование InjectionToken() для объекта window
Ниже простой пример создания InjectionToken
:
import { InjectionToken } from '@angular/core'; export const WINDOW = new InjectionToken<Window>('Global window object', { factory: () => window });
Здесь мы создаем новый InjectionToken
, передавая два параметра:
2) Объект параметров, который настраивает поведение токена
Объект параметров использует свойство factory для указания того, что токен будет возвращать глобальный объект window при инъекции в артефакт Angular. Чтобы использовать только что созданный InjectionToken
, мы используем декоратор Inject
в компоненте Angular следующим образом:
import { Component, Inject } from '@angular/core'; import { WINDOW } from './window'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(@Inject(WINDOW) private _window: Window) {} }
До этого момента мы написали много кода для использования нативного JavaScript API. В качестве альтернативы мы можем использовать существующую функцию фреймворка Angular для выполнения той же задачи. Angular предоставляет нам DOCUMENT
, еще один встроенный InjectionToken
, который дает нам доступ к текущему объекту документа окна:
Объект document
в нативном JavaScript содержит свойство defaultView
, которое возвращает окно, связанное с текущим документом. Теперь можно переписать компонент Angular следующим образом:
import { DOCUMENT } from '@angular/common'; import { Component, Inject } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(@Inject(DOCUMENT) private document: Document) { console.log(document.defaultView); } }
В предыдущем фрагменте мы импортируем токен DOCUMENT
и используем его так же, как делали с нашим пользовательским токеном window
. Недостаток этого подхода заключается в том, что свойство defaultView
может вызывать путаницу, потому что оно не явно указывает, что мы обращаемся к объекту window
.