Как разработчики 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, который дает нам доступ к текущему объекту документа окна:
Объект 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.
Больше материалов с хорошей подсветкой синтаксиса в моем блоге на teletype