Integrating Maps into your Angular Application with Leaflet[in-process]
Tags: angular2, leaflet, maps
Published: 2021-06-03 19:59:57

Jim Armstrong | ng-conf | Oct 2019

This article is oriented to beginning Angular developers wishing to integrate [Leaflet](https://leafletjs.com/) into their applications. The integration of third-party dependencies into Angular can be anywhere from trivial to problematic. While Leaflet tends more towards the trivial side, maps that fluidly adjust with browser size can present some setup problems relative to the Angular lifecycle.

Before starting the deconstruction, point your friendly, neighborhood browser to the following Github.

theAlgorithmist/Angular8-Leaflet](https://github.com/theAlgorithmist/Angular8-Leaflet) on github.com

Now, you can follow along or fork the project and use it as a base for your own applications.

#### Background

This tutorial presumes a small amount of prior Leaflet knowledge, but no more than is expressed in the [Leaflet Quickstart Guide](https://leafletjs.com/examples/quick-start/).

[ArcGis](https://www.npmjs.com/package/esri-leaflet) is used for tiling in the application covered in this article (the esri-_leaflet_ package is installed from npm).

#### Angular Setup

Very little needs to be done after the ubiquitous _npm install_. Necessary Leaflet styles are made available through the _angular.json_ file,

```
"styles": [
  "src/styles.scss",
  "./node_modules/leaflet/dist/leaflet.css"
],
```

I’ve had mixed results with _@types/leaflet_, so this application does not exploit typings for sake of simplicity. Leaflet and ESRI tiles are imported as shown below,

```
import * as esri from 'esri-leaflet';
import * as L from 'leaflet';
```

A Leaflet Map, for example, is temporarily typed as _any_; feel free to firm up typings as an exercise after you have deconstructed the tutorial.

Coordinates of a map’s center are important for map initialization. Many demos hardcode this information. As an alternative, this tutorial illustrates how to inject map-center coordinates using Angular’s [Injection Tokens](https://angular.io/api/core/InjectionToken).

A coordinate is a two-dimensional point on a map, represented by latitude and longitude. An InjectionToken for a coordinate may be created as illustrated in the _/src/app/tokens.ts file_,

```
import { InjectionToken } from '@angular/core';

export const INIT_COORDS = new InjectionToken<{lat: number, long: number}>('INIT_COORDS');
```

_INIT_COORDS_ is used in the /src/app/app.module.ts file to perform the actual (lat/long) provision (or definition),

```
providers: [
  { provide: INIT_COORDS, useValue: {lat: 32.9756, long: -96.89} }
],
```

This moves the definition of the map center to the highest level in the Angular application (where it is easily seen and almost self-documenting).

In order to inject this coordinate into the application, import _INIT_COORDS_ from the tokens file and inject from a class constructor. This is illustrated in the _/src/app/map.component.ts_ file (the primary map component in this tutorial),

```
constructor( @Inject(INIT_COORDS) protected _initCoords: {lat: number, long: number} )
```

#### Map Component

The application covered in this article displays a fixed-height map with a width that extends across the entire browser window. The map redraws as the window resizes. Some markers are also rendered on top of the map tiles.

The map is integrated into the application through the main app component’s template, as shown below

_/src/app/app.component.html_

```
<app-map [markers]="markers"></app-map>
```

and the map component’s source code is found in _/src/app/maps/map.component.ts_.

The map is rendered into a container (DIV) that is currently in the map component’s template,

_/src/app/maps/map.component.html_

```
<div>

  <div id="map-container">
    <div #primaryMap [style.height.px]="currentHeight" [style.width.px]="currentWidth"></div>

    <map-control class="map-control"></map-control>

    <div class="mouse-coords">
      <span id="mouse-coords-label"></span>
      <span [innerHTML]="mcText"></span>
    </div>
  </div>

</div>
```

If the width and height of the containing DIV are fixed, then map tiles can be rendered at an early point in the component lifecycle. Two approaches are possible for passing variable width and height, _outside-in_ and _inside-out_. The _outside-in_ method defines Angular _Inputs_ for width and height. The parent component is responsible for communicating width and height to the map component. The _inside-out_ approach defines width and height internal to the map component and ‘passes’ that information to its template. Most applications use the outside-in approach. This tutorial employs the _inside-out_ method for purposes of illustration. Modify the code to change the passing of width and height as an exercise.

Variable width and height introduce some subtle issues relative to map rendering. It is tempting to perform everything related to Leaflet setup in the on-init handler, and this typically works with fixed width and height. Since map width varies with browser size in this tutorial, it is better to separate the map initialization process between the on-init and after-view-init lifecycle handlers. This is illustrated in the following code segments from _/src/app/maps/map.component.ts_.

First, the identification of a containing DIV for map initialization is offloaded to a _ViewChild_,

```
@ViewChild('primaryMap', {static: true}) protected mapDivRef: ElementRef;
protected mapDiv: HTMLDivElement;
```

Most of the map setup is performed in _ngOnInit_,

```
public ngOnInit(): void
{
  // Reference to DIV containing map is used in Leaflet initialization
  this.mapDiv = this.mapDivRef.nativeElement;

  this.__initializeMap();
  this.__renderMap();
  this.__showMarkers();
}
```

Invalidating the map size is deferred to _ngAfterViewInit_, when the full width and height necessary for the map tiles to work is available. The _map_ variable refers to the Leaflet Map created in the _ngOnInit_ handler.

```
public ngAfterViewInit(): void
{
  this.map.invalidateSize();

  this.__initMapHandlers();
}
```

Note that the update map size handler recomputes the class _currentWidth_ and _currentHeight_ variables, which are bound to the DIV styles that control the width and height of the map DIV. Leaflet uses that width/height information to re-tile and re-render the map.

Since map width/height definition and resizing the map with window changes are handled internal to the map component, a simple window-resize handler is added.

```
@HostListener('window:resize', ['$event'])
protected __onResize(event: any): void
{
  this.__updateMapSize();

  this.map.invalidateSize();
}
```

This could also be handled by an outside service, run outside Angular, and debounced. If there is sufficient interest, I will provide a tutorial that illustrates the technique.

#### Map Controls

Leaflet does allow custom controls(this will be a link), but you may also use Angular components as map controls. Create the Angular component and use CSS for positioning. I’ve used this in several projects for highly customized zoom controls. See the _/src/app/maps/map-control_ folder for a skeleton that may be used as a starting point.

Refer to _/src/app/maps/map.component.scss_ for relevant styling.

#### Beware the z-index

If tiling or some other aspect of map display does not appear to work, it may be an issue with z-index. In the past, I’ve had to re-map z-indices simply from changing tile providers or even migrating from Angular 7 to Angular 8!

#### Map Markers

Markers with known coordinates may be added to a map. Define the coordinates and then create a Leaflet _Icon_ for each one. Marker coordinates for this tutorial are defined in the main app component,

_/src/app/app.component.ts_

```
public markers: {lat: number, long: number}[];   // Map markers (relevance depends on map center)

constructor()
{
  // some map markers
  this.markers = [
    { lat: 32.9756, long: -96.89   },
    { lat: 33.1543, long: -96.8352 },
    { lat: 32.93  , long: -96.8195 },
    { lat: 32.8998, long: -97.0403 },
    { lat: 33.0737, long: -96.3697 },
    { lat: 33.1014, long: -96.6744 }
  ];
}
```

and are passed as an Angular _Input_ to the Map Component,

_/src/app/app.component.html_

```
<app-map [markers]="markers"></app-map>
```

Markers are rendered in the map component,

_/src/app/maps/map.component.ts_

```
protected __showMarkers(): void
{
  if (this.markers !== undefined && this.markers != null && this.markers.length > 0)
  {
    // Add markers
    const icon = L.icon({
      iconUrl: MapIconOptions.mapIcon,
      iconSize: MapIconOptions.iconSize,
      iconAnchor: MapIconOptions.iconAnchor,
      shadowUrl: MapIconOptions.mapShadowIcon,
      shadowSize: MapIconOptions.shadowSize,
      shadowAnchor: MapIconOptions.shadowAnchor,
    });

    const n: number = this.markers.length;
    let i: number;
    let m: L.marker;

    let x: number;
    let y: number;

    for (i = 0; i < n; ++i) {

      x = this.markers[i].lat;
      y = this.markers[i].long;

      if (x !== undefined && !isNaN(x) && y !== undefined && !isNaN(y))
      {
        // okay to add the icon
        m = L.marker([x, y], {icon: icon}).addTo(this.map);
      }
      else
      {
        // implement your own error handling
        console.log('MARKER ERROR, Marker number: ', (i+1), 'x: ', x, ' y: ', y);
      }
    }
  }
}
```

#### Interactivity

A couple handlers for basic map interactivity have been added for you, namely mouse-click and mouse-move. The latter updates the current map position (lat/long) based on the mouse position.

_/src/app/maps/map.component.ts_

```
protected __onMapMouseMove(evt: any): void
{
  const lat: string  = evt.latlng.lat.toFixed(3);
  const long: string = evt.latlng.lng.toFixed(3);
  this.mcText        = `Latitude: ${lat} &nbsp; &nbsp; Longitude: ${long}`;
}
```

The string reflecting the current position is displayed in the map component’s template,

/src/app/maps/map.component.html

```
<div class="mouse-coords">
  <span id="mouse-coords-label"></span>
  <span [innerHTML]="mcText"></span>
</div>
```

#### The Result

Build the application and you should observe something similar to this screenshot,

![Image for post](https://miro.medium.com/max/700/1*zUaR20P6whJTm9a-PPQ_kw.png)
###### Angular version 8 and Leaflet Map

Move the mouse over the map to observe updates of the mouse position in map coordinates.

I hope this gets you started on working with Angular and Leaflet. These two applications work very well together and there is near-limitless potential for highly interactive, engaging maps.

Good luck with your Angular efforts!

For more Angular goodness, be sure to check out the latest episode of [The Angular Show podcast]
(https://www.spreaker.com/show/angular-show).

---
## ng-conf: Join us for the Reliable Web Summit
Come learn from community members and leaders the best ways to build reliable web applications, write quality code, choose scalable architectures, and create effective automated tests. Powered by ng-conf, join us for the Reliable Web Summit this August 26th & 27th, 2021.
https://reliablewebsummit.com/