Angular

ngx-apexmaps is a standalone Angular component around the imperative ApexMaps class. It is zoneless-ready: the map is built, rendered, and updated outside the Angular zone, and its outputs re-enter the zone to emit, so the same code repaints correctly in zoned and zoneless applications alike.

Installation

npm install apexmaps ngx-apexmaps

apexmaps and @angular/core (>=20.0.0) are the only peer dependencies. There is no @angular/common requirement, no rxjs requirement of its own, no zone.js requirement, and no NgModule.

Usage

import { Component, signal } from '@angular/core'
import { ApexMapsComponent } from 'ngx-apexmaps'
import 'apexmaps/apexmaps.css'

@Component({
  standalone: true,
  imports: [ApexMapsComponent],
  template: `
    <apx-map
      [options]="options"
      [series]="series()"
      [height]="480"
      (featureClick)="selected.set($event.key)"
    />
  `,
})
export class CoverageComponent {
  readonly options = { geo: { map: 'world' } }
  readonly series = signal([
    {
      type: 'choropleth',
      name: 'Coverage',
      data: [{ key: 'IND', value: 42 }],
      scale: { palette: 'blues', classes: 5 },
    },
  ])
  readonly selected = signal<string | null>(null)
}

Inputs and outputs

InputTypeNotes
optionsApexMapsOptionsRequired signal input. The same options object the core takes.
seriesSeries[]Optional signal input, shorthand for options.series. Takes precedence over it; both routes reach updateSeries(), so both tween.
width / heightnumber | stringSignal inputs, shorthand for options.chart.width / .height. See Sizing below.

One output per core event, under the core's own name: rendered, updated, resized, featureClick, featureHover, featureFocus, markClick, markHover, clusterClick, drilldown, drillup, selectionChange, legendToggle, zoom, panEnd. Each carries the payload the core emits, fully typed; see API: Methods and Events for every payload shape.

The zone contract

  • The map runs outside the Angular zone. A map attaches pointer listeners to everything it draws, and zone.js patches addEventListener, so a map built inside the zone would run change detection on every pointer movement over every feature. Construction, rendering, and updates all happen outside the zone.
  • Outputs are emitted back inside the zone. Events originate from unpatched listeners, which zone-based change detection cannot see on its own, so the component calls NgZone.run() around each emit. A (featureClick) handler that sets component state then repaints, whether the app is zoned or zoneless. Under zoneless change detection, NgZone degrades to a no-op and both calls collapse to plain function calls, so the same code is correct either way.

Measured against the built package: 30 pointer moves over the map produce zero change-detection passes; one click produces one repaint.

The imperative API

Camera moves, drilldown, export, and diagnostics are methods, not options. The component exposes the live instance as a signal, reachable through a template variable on the element:

@Component({
  template: `<apx-map #map [options]="options" />
    <button (click)="map.map()?.frameFeature('IND')">Zoom to India</button>`,
  imports: [ApexMapsComponent],
  standalone: true,
})
export class ZoomDemo {
  readonly options = { geo: { map: 'world' } }
}

map() is null until the first render completes and after destroy, so an effect() on it is the way to run something exactly once the map exists.

Change detection

Bindings are compared deeply, not by reference, because a template expression like [options]="build()" hands over a brand new object on every change-detection cycle. A fresh but equal tree is not a redraw. An inline formatter is compared by source rather than identity, and geo.map is compared by identity and never walked, so pass a stable reference or a registry id string for it. A series-only change routes to updateSeries(), which tweens; everything else routes to updateOptions(), which redraws.

Signal inputs do not fire on in-place mutation, so prefer replacing objects rather than mutating them. If some other input changes afterward, an earlier in-place mutation is picked up along with it rather than lost.

Sizing

Use the width and height inputs, not CSS. The map's height comes from options.chart.height, which defaults to 400; an explicit number wins over the container. Width defaults to '100%' and follows the container. height="100%" hands the height to the container:

<apx-map [options]="options" height="100%" style="height: 60vh" />

<apx-map> itself is display: block by default, and it is the element your class and style bindings land on. The element inside it belongs to the map.

SSR

Safe under Angular SSR and hydration: the component renders its container on the server and builds the map in afterNextRender, which only ever runs in the browser. There is no server-rendered map markup yet, because the core has no HTML output path.