npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@angular-helpers/openlayers

v0.4.0

Published

Modern Angular wrapper for OpenLayers with modular architecture, standalone components, and hybrid template/programmatic API

Readme

@angular-helpers/openlayers

A modern Angular wrapper for OpenLayers with modular architecture, standalone components, and a hybrid template/programmatic API.

Features

  • Standalone Components - No NgModule boilerplate, use directly in your components
  • Signals Integration - Native Angular signals for reactive state management
  • Modular Loading - Import only what you need with AgGrid-style sub-entry points
  • Dual API - Template for UI, Inputs for data, Services for operations
  • Tree-shaking - Unused OpenLayers code is eliminated from your bundle
  • TypeScript First - Full type safety with strict mode support
  • Military Features - Ellipses, sectors, NATO symbology, and MGRS coordinates

Installation

npm install @angular-helpers/openlayers ol

Quick Start

1. Configure in your app

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideOpenLayers } from '@angular-helpers/openlayers/core';
import { withLayers } from '@angular-helpers/openlayers/layers';
import { withControls } from '@angular-helpers/openlayers/controls';

export const appConfig: ApplicationConfig = {
  providers: [provideOpenLayers(withLayers(), withControls())],
};

2. Use in your component

// map.component.ts
import { Component, inject, signal } from '@angular/core';
import { OlMapComponent } from '@angular-helpers/openlayers/core';
import { OlTileLayerComponent } from '@angular-helpers/openlayers/layers';
import {
  OlZoomControlComponent,
  OlScaleLineControlComponent,
} from '@angular-helpers/openlayers/controls';
import { OlMapService } from '@angular-helpers/openlayers/core';

@Component({
  selector: 'app-map',
  standalone: true,
  imports: [
    OlMapComponent,
    OlTileLayerComponent,
    OlZoomControlComponent,
    OlScaleLineControlComponent,
  ],
  template: `
    <ol-map
      [center]="center()"
      [zoom]="zoom()"
      (viewChange)="onViewChange($event)"
      (click)="onMapClick($event)"
      style="display: block; height: 400px;"
    >
      <!-- Base Layer -->
      <ol-tile-layer id="osm" source="osm"></ol-tile-layer>

      <!-- Controls -->
      <ol-zoom-control></ol-zoom-control>
      <ol-scale-line-control unit="metric"></ol-scale-line-control>
    </ol-map>

    <p>Clicked: {{ lastClick()?.coordinate | json }}</p>
  `,
})
export class MapComponent {
  private mapService = inject(OlMapService);

  center = signal<[number, number]>([2.2945, 48.8584]);
  zoom = signal<number>(12);
  lastClick = signal<{ coordinate: [number, number]; pixel: [number, number] } | null>(null);

  onViewChange(state: { center: [number, number]; zoom: number }): void {
    this.center.set(state.center);
    this.zoom.set(state.zoom);
  }

  onMapClick(event: { coordinate: [number, number]; pixel: [number, number] }): void {
    this.lastClick.set(event);
  }

  flyToEiffel() {
    this.mapService.animateView({ center: [2.2945, 48.8584], zoom: 15, duration: 1000 });
  }
}

Overlays — popups and tooltips

Available since 0.3.0 from @angular-helpers/openlayers/overlays.

<ol-popup> — declarative popup with content projection

The popup's host element is used directly as the underlying ol/Overlay element, so projected children stay inside Angular's view tree and benefit from change detection without any extra plumbing.

import { OlPopupComponent } from '@angular-helpers/openlayers/overlays';

@Component({
  imports: [OlMapComponent, OlVectorLayerComponent, OlPopupComponent],
  template: `
    <ol-map [center]="[2.17, 41.38]" [zoom]="12">
      <ol-vector-layer id="cities" [features]="cities()" />

      <ol-popup
        [position]="selectedCoord()"
        [closeButton]="true"
        [autoPan]="true"
        (closed)="clearSelection()"
      >
        <h3>{{ selected()?.name }}</h3>
        <p>{{ selected()?.description }}</p>
      </ol-popup>
    </ol-map>
  `,
})
export class MyMap {
  // …
}

Setting [position]="null" hides the popup and emits closed.

[olTooltip] — feature hover tooltip

<ol-vector-layer
  id="cities"
  [features]="cities()"
  [olTooltip]="'name'"
  [olTooltipLayer]="'cities'"
/>

Reads feature.get('name') for any feature on layer cities under the cursor and renders a styled <div role="tooltip"> near the pointer. Use the .ol-tooltip class to override the default look.

OlPopupService — programmatic popups

Three content modes from a service:

const popups = inject(OlPopupService);

// 1) Plain text / HTMLElement
popups.open({
  id: 'simple',
  position: [2.17, 41.38],
  content: 'Hello map',
  positioning: 'bottom-center',
  autoPan: true,
});

// 2) Dynamic Angular component (createComponent + hostElement)
const handle = popups.openComponent({
  id: 'city-popup',
  position: [2.17, 41.38],
  component: CityCardComponent,
  bindings: [
    inputBinding('city', () => selected()),
    outputBinding<void>('closed', () => handle.close()),
  ],
});

open is idempotent by id and updates the existing overlay in place. openComponent always recreates the ComponentRef on a repeated id and disposes the previous one (appRef.detachView + ref.destroy) to avoid CD leaks. Calls made before the map is ready are queued and replayed on OlMapService.onReady.

Military symbology

Available since 0.4.0 from @angular-helpers/openlayers/military.

Three pure-math geometry helpers (no extra deps) plus a NATO MIL-STD-2525 symbol helper backed by the optional milsymbol peer dependency.

import { inject, signal } from '@angular/core';
import { OlMilitaryService } from '@angular-helpers/openlayers/military';
import type { Feature } from '@angular-helpers/openlayers/core';

@Component({
  // …
  imports: [OlMapComponent, OlVectorLayerComponent],
  template: `
    <ol-map [center]="[2.17, 41.38]" [zoom]="8">
      <ol-tile-layer id="osm" source="osm" />
      <ol-vector-layer id="military" [features]="features()" [zIndex]="10" />
    </ol-map>
  `,
})
export class MilDemo {
  private ml = inject(OlMilitaryService);
  features = signal<Feature[]>([]);

  async ngOnInit() {
    const ellipse = this.ml.createEllipse({
      center: [2.17, 41.38],
      semiMajor: 6_000,
      semiMinor: 3_000,
      rotation: Math.PI / 6,
    });
    const sector = this.ml.createSector({
      center: [-0.38, 39.47],
      radius: 8_000,
      startAngle: Math.PI / 6,
      endAngle: Math.PI / 2,
    });
    const donut = this.ml.createDonut({
      center: [-5.99, 37.39],
      innerRadius: 5_000,
      outerRadius: 10_000,
    });
    const symbol = await this.ml.createMilSymbol({
      sidc: 'SFGPUCI-----',
      position: [-3.7, 40.42],
      size: 36,
    });
    this.features.set([ellipse, sector, donut, symbol]);
  }
}

Geometry helpers

| Method | Output | Notes | | ----------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | | createEllipse(config) | Feature<Polygon> | Optional rotation in radians, configurable segments (default 64) | | createSector(config) | Feature<Polygon> | Pie-slice (apex-arc-apex). startAngle < endAngle ≤ start + 2π | | createDonut(config) | Feature<Polygon> | Two rings: outer CCW, inner CW (right-hand rule). Renders as an annular band with the basemap visible through the hole |

Coordinates are emitted in EPSG:4326 (lon/lat) using a local tangent-plane projection. Accurate up to ~100 km from the center; for very large radii or polar regions, geodesic-correct math is on the Phase 3 roadmap.

MIL-STD-2525 symbols

createMilSymbol lazy-loads milsymbol on first use and returns a Feature<Point> with style metadata (feature.style.icon) so the vector layer renders it as an ol/style/Icon. The library is declared as an optional peer dependency — install it only if you use this helper:

npm install milsymbol
const symbol = await ml.createMilSymbol({
  sidc: 'SFGPUCI-----', // friendly infantry, ground unit
  position: [-3.7, 40.42],
  size: 36,
  uniqueDesignation: 'A1',
});

Three flavors:

  • createMilSymbol(config) — async; lazy-loads on first call.
  • createMilSymbolSync(config) — sync; throws if milsymbol is not loaded yet.
  • preloadMilsymbol() — fire-and-forget on app init to make the first symbol render synchronous.

The service throws clearly on non-browser environments (createMilSymbol requires window).

Architecture

Data vs UI Separation

| Use Case | Approach | Example | | ------------------------------------ | --------------------------- | ------------------------------------------------------------- | | Data (features, layers, coords) | @Input() | <ol-vector-layer [features]="data"> | | UI (buttons, popups, toolbars) | Template/content projection | <ol-custom-control><button>...</button></ol-custom-control> | | Operations (animations, queries) | Service via inject() | this.ol.animateView({...}) |

Sub-Entry Points

Import only what you need:

// Core only (~45KB gzipped)
import { OlMapComponent, OlMapService } from '@angular-helpers/openlayers';

// Add layers (~35KB additional)
import { OlVectorLayerComponent, withLayers } from '@angular-helpers/openlayers/layers';

// Add controls (~15KB additional)
import { OlCustomControlComponent, withControls } from '@angular-helpers/openlayers/controls';

// Add interactions (~40KB additional)
import {
  OlDrawInteractionComponent,
  withInteractions,
} from '@angular-helpers/openlayers/interactions';

// Add military features — pure-math helpers + lazy-loaded milsymbol
import { OlMilitaryService, withMilitary } from '@angular-helpers/openlayers/military';

API Reference

See the documentation for full API reference and examples.

Comparison with Other Libraries

| Feature | ngx-openlayers | ngx-ol-library | IGO2-lib | This Library | | --------------------- | -------------- | -------------- | -------- | ------------------------ | | Angular 21+ | ❌ | ✅ | ❌ | ✅ | | Standalone Components | ❌ | ✅ | ❌ | ✅ | | Signals | ❌ | ❌ | ❌ | ✅ | | Modular Loading | ❌ | Partial | ❌ | ✅ | | Programmatic API | ❌ | ❌ | Partial | ✅ | | Military Features | ❌ | ❌ | ❌ | ✅ | | Bundle Size | ~180KB | ~200KB | ~500KB | ~145KB (all modules) |

License

MIT

Contributing

See the main repository for contribution guidelines.