angular
February 17

Как использовать 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, передавая два параметра:

1) Описание токена

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, который дает нам доступ к текущему объекту документа окна:

https://angular.dev/api/common/DOCUMENT

Объект 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.