Архитектура взаимодействия HERE Technologies JavaScript API с Angular требует разделения ответственности между DOM-уровнем и логикой карты. Основной инструмент такого разделения — директивы Angular, позволяющие привязывать поведение карты к элементам шаблона без нарушения компонентной структуры приложения.
Карта HERE Maps строится вокруг объекта H.Map, который
требует привязки к DOM-узлу. В Angular это приводит к необходимости
получения ссылки на элемент через ElementRef и управления
жизненным циклом через хуки OnInit и
OnDestroy.
Создание директивы начинается с определения контейнера, в который будет рендериться карта:
import { Directive, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
declare const H: any;
@Directive({
selector: '[hereMap]'
})
export class HereMapDirective implements OnInit, OnDestroy {
@Input() apiKey!: string;
@Input() center: { lat: number; lng: number } = { lat: 0, lng: 0 };
@Input() zoom: number = 10;
private platform: any;
private map?: any;
constructor(private el: ElementRef) {}
ngOnInit(): void {
this.platform = new H.service.Platform({
apikey: this.apiKey
});
const defaultLayers = this.platform.createDefaultLayers();
this.map = new H.Map(
this.el.nativeElement,
defaultLayers.vector.normal.map,
{
center: this.center,
zoom: this.zoom,
pixelRatio: window.devicePixelRatio || 1
}
);
const beh * avior = new H.mapevents.Behavior(
new H.mapevents.MapEvents(this.map)
);
H.ui.UI.createDefault(this.map, defaultLayers);
}
ngOnDestroy(): void {
if (this.map) {
this.map.dispose();
}
}
}
Ключевым моментом является привязка
this.el.nativeElement к экземпляру карты. Это позволяет
использовать директиву как декларативный контейнер.
Директива превращает HTML-элемент в полноценный контейнер карты:
<div
hereMap
[apiKey]="HERE_API_KEY"
[center]="{ lat: 49.988, lng: 36.232 }"
[zoom]="12"
class="map-container"
></div>
Такой подход исключает необходимость ручного управления DOM внутри компонентов и переносит всю инициализацию в директивный слой.
Angular позволяет динамически изменять параметры карты через
@Input, однако сама карта не реагирует автоматически на
изменения. Требуется ручная синхронизация через
ngOnChanges.
import { SimpleChanges, OnChanges } from '@angular/core';
export class HereMapDirective implements OnInit, OnDestroy, OnChanges {
@Input() center!: { lat: number; lng: number };
@Input() zoom!: number;
private map: any;
ngOnChanges(changes: SimpleChanges): void {
if (!this.map) return;
if (changes['center'] && this.center) {
this.map.setCenter(this.center);
}
if (changes['zoom'] && this.zoom !== undefined) {
this.map.setZoom(this.zoom);
}
}
}
Таким образом директива выступает адаптером между реактивной моделью Angular и императивным API HERE Maps.
Работа с объектами на карте в HERE Maps осуществляется через
H.map.Marker. Инкапсуляция этой логики в директиве
позволяет управлять маркерами декларативно.
@Input() markers: { lat: number; lng: number }[] = [];
private uiMarkers: any[] = [];
private renderMarkers(): void {
if (!this.map) return;
this.uiMarkers.forEach(m => this.map.removeObject(m));
this.uiMarkers = [];
this.markers.forEach(point => {
const marker = new H.map.Marker(point);
this.map.addObject(marker);
this.uiMarkers.push(marker);
});
}
Обновление маркеров также привязывается к ngOnChanges,
обеспечивая синхронное обновление визуального слоя.
События карты интегрируются через систему H.mapevents. В
Angular их удобно преобразовывать в EventEmitter, чтобы
пробрасывать наружу.
import { Output, EventEmitter } from '@angular/core';
@Output() mapClicked = new EventEmitter<{ lat: number; lng: number }>();
private attachEvents(): void {
this.map.addEventListener('tap', (evt: any) => {
const coord = this.map.screenToGeo(
evt.currentPointer.viewportX,
evt.currentPointer.viewportY
);
this.mapClicked.emit({
lat: coord.lat,
lng: coord.lng
});
});
}
Такой подход позволяет изолировать API событий карты от компонентов приложения.
При усложнении логики директива начинает зависеть от сервисного слоя, который управляет платформой и общими ресурсами HERE Maps.
import { Injectable } from '@angular/core';
declare const H: any;
@Injectable({ providedIn: 'root' })
export class HereMapService {
createPlatform(apiKey: string): any {
return new H.service.Platform({ apikey: apiKey });
}
createMap(platform: any, element: HTMLElement, config: any): any {
const layers = platform.createDefaultLayers();
return new H.Map(element, layers.vector.normal.map, config);
}
}
Директива при этом перестаёт отвечать за создание инфраструктуры и концентрируется на DOM-уровне.
HERE Maps API требует явного освобождения ресурсов. В Angular это критично из-за повторного создания компонентов.
ngOnDestroy(): void {
if (this.map) {
this.map.removeObjects(this.map.getObjects());
this.map.dispose();
}
}
Игнорирование очистки приводит к накоплению WebGL-контекстов и утечкам памяти при навигации по маршрутам приложения.
В Angular часто требуется синхронизация карты с потоками данных. Директива может подписываться на Observable координат:
@Input() center$?: any;
private subscription: any;
ngOnInit(): void {
if (this.center$) {
this.subscription = this.center$.subscribe((coord: any) => {
if (this.map) {
this.map.setCenter(coord);
}
});
}
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
Это позволяет интегрировать карту в архитектуру потоков данных без нарушения инкапсуляции.
При расширении функциональности директива превращается в ядро управления картографическим слоем:
tile layers,
traffic layer)Каждый слой добавляется как отдельная абстракция поверх
H.Map, что позволяет сохранять предсказуемость поведения
при росте сложности интерфейса.
Карта HERE Maps не всегда автоматически реагирует на изменение
размеров контейнера. В Angular это решается через
ResizeObserver:
private resizeObserver?: ResizeObserver;
private initResizeObserver(): void {
this.resizeObserver = new ResizeObserver(() => {
if (this.map) {
this.map.getViewPort().resize();
}
});
this.resizeObserver.observe(this.el.nativeElement);
}
ngOnDestroy(): void {
this.resizeObserver?.disconnect();
}
Это обеспечивает корректное отображение карты при изменении layout.
Директивная модель Angular позволяет использовать несколько карт на одной странице. В этом случае важно изолировать платформу или переиспользовать её:
const sharedPlatform = new H.service.Platform({ apikey: this.apiKey });
this.map = new H.Map(
element,
sharedPlatform.createDefaultLayers().vector.normal.map,
config
);
Переиспользование платформы снижает накладные расходы на инициализацию и ускоряет рендеринг.
При усложнении интерфейса директива используется только для инициализации карты, а логика переносится в компонент:
H.MapТакая архитектура обеспечивает предсказуемую масштабируемость при работе с HERE Technologies API внутри больших Angular-приложений.