Appearance
MapstedMapService — Angular injectable service
This recipe provides an injectable MapstedMapService that wraps the init / destroy lifecycle and makes the MapInstance available throughout your Angular application.
For the full tutorial covering Angular Universal SSR, @ViewChild, and NgModule vs standalone components, see Embed with Angular.
The service
typescript
// src/app/services/mapsted-map.service.ts
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { BehaviorSubject } from 'rxjs';
import { init } from '@mapsted/maps-js-api';
import type { MapInstance } from '@mapsted/maps-js-api';
@Injectable({ providedIn: 'root' })
export class MapstedMapService {
private mapInstance: MapInstance | null = null;
/** Emits `true` once the map is fully loaded and ready for commands. */
readonly ready$ = new BehaviorSubject<boolean>(false);
constructor(@Inject(PLATFORM_ID) private platformId: object) {}
/** Initialize the map inside the given container element. */
async mount(element: HTMLElement, propertyId: number): Promise<void> {
if (!isPlatformBrowser(this.platformId)) return;
this.mapInstance = await init({
element,
propertyId,
onload: () => this.ready$.next(true),
});
}
/** Destroy the map and reset state. Call from `ngOnDestroy`. */
async unmount(): Promise<void> {
await this.mapInstance?.destroy();
this.mapInstance = null;
this.ready$.next(false);
}
/** Returns the raw MapInstance for direct API calls. */
getInstance(): MapInstance | null {
return this.mapInstance;
}
}Component using the service
Angular 17+ required
The @if control-flow syntax shown below requires Angular 17 or later. For Angular 14–16, replace @if (mapService.ready$ | async) { … } with <ng-container *ngIf="mapService.ready$ | async">…</ng-container> and add NgIf to the standalone component's imports: [AsyncPipe, NgIf].
typescript
// src/app/components/map-view/map-view.component.ts
import {
Component,
Input,
ViewChild,
ElementRef,
AfterViewInit,
OnDestroy,
} from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { MapstedMapService } from '../../services/mapsted-map.service';
import { navigateToFloorById } from '@mapsted/maps-js-api';
@Component({
selector: 'app-map-view',
standalone: true,
imports: [AsyncPipe],
template: `
<div #mapContainer style="width: 100%; height: 100vh;"></div>
@if (mapService.ready$ | async) {
<button (click)="handleNavigate()">Go to floor</button>
}
`,
})
export class MapViewComponent implements AfterViewInit, OnDestroy {
@Input() propertyId!: number;
@ViewChild('mapContainer') mapContainer!: ElementRef<HTMLDivElement>;
constructor(public mapService: MapstedMapService) {}
async ngAfterViewInit(): Promise<void> {
await this.mapService.mount(this.mapContainer.nativeElement, this.propertyId);
}
async ngOnDestroy(): Promise<void> {
await this.mapService.unmount();
}
handleNavigate(): void {
navigateToFloorById(42);
}
}Notes
providedIn: 'root': The service is a singleton. If you mount more than one map on the same page, create separate service instances using component-levelproviders: [MapstedMapService]instead of the root injector.ready$observable:BehaviorSubject<boolean>starts asfalseand emitstrueonceonloadfires. Bind it with theasyncpipe in templates to conditionally show controls.- SSR guard: The
isPlatformBrowserguard inmount()ensuresinit()is only called in the browser, making the service safe for Angular Universal applications. getInstance(): Returns the rawMapInstanceso you can call any Maps JS API method directly. Import named functions from@mapsted/maps-js-api— they operate on the module-scoped singleton automatically.