Adding maps to an #Angular application using the #Leaflet library

If you are looking to add maps to your Angular website or application a great option that I’ve used on a few projects is Leaflet.js. The library provides features like custom markers, ability to use various map sources, multiple layers, and much more. In addition to that it is also free, open source, and actively maintained. The project has been around for years and has an active community continuing to extend it with numerous plugins. The majority of documentation out there for the library focuses on using it in a vanilla JavaScript project so in this post we’re going to explore adding Leaflet.js to an Angular Ivy project.

In this application we’ll create a component that will hold the map object. It will be configured to always show Europe when it starts up but also provide a button to pan the map to a specific location. The user can change the zoom or move the map around as they wish. There will also be callbacks setup to execute when the user changes the zoom level or the center of the map. You can see the complete project on GitHub. A running example can also be seen on StackBlitz.

To start off, if you don’t already have an Angular app to add the mapping feature to, create a new one.

$ ng new leaflet-map-example

Once the project is initialized we will need to add references to Leaflet and its Typings definition to the project. This can be done by opening the package.json file and adding
"leaflet": "^1.6.0"
to the dependencies section. In devDependencies add
"@types/leaflet": "^1.5.7"

Another location that needs to be updated is the angular.json file. Under "projects" --> "demo" --> "architect" --> "build" --> "options" --> "assets" add this code that will copy leaflet assets out to the leaflet folder during the build process.

{
    "glob": "**/*",
    "input": "./node_modules/leaflet/dist/images",
    "output": "leaflet/"
}

Also, just below the "assets" section should be the "styles" section. In this section add this line so we can bring the Leaflet styles over to the application during the build.

"./node_modules/leaflet/dist/leaflet.css"

Next we’ll want to create a new component that will hold our logic for this map. From the command line run:

$ ng generate component map

We should now have a new folder in our project called map that contains three files: map.component.css, map.component.html, map.component.ts. The majority of our work will be in the TypeScript file but we still do need to add some code to the CSS and HTML files.

In map.component.html we’ll add two <div> tags, one to encompass the HTML for the page and another to hold the map. At the bottom of the page we will have a button that calls a function in the TypeScript code to pan the map on a specific location.

<div class="map-container">
  <div id="map"></div>
  <br />
  <button (click)="centerMap(39.95, -75.16)">Locate Philadelphia, PA</button>
</div>

Next we’ll add some CSS to define the size and add a border to the map in map.component.css.

#map{
  border: 2px solid black;
  height: 400px;
  width: 100%;
}

With the scaffolding in place we can now focus on map.component.ts to add the code which will instantiate and customize the actual map. The first line of code we’ll add is to import the Leaflet library at the top of the file.

import * as L from "leaflet";

Within the class definition add a variable to hold the map object.

map: L.Map;

We’ll setup the actual actual initialization of the map object and tie it to the HTML DOM in the ngOnInit() function.

The map object will be configured to center the map on a latitude and longitude over Europe. We’ll have the zoom level set to 4 and restrict the permitted zoom levels to be between 1 and 10. Besides those properties the most important part of the initialization is the first parameter, 'map'. This value matches up to the ID field in one of the map.component.html <div> elements and tells Leaflet to use that element to render the map.

// Initialize the map to display Europe    
this.map = L.map('map',
 {
      center: [49.8282, 8.5795],
      zoom: 4,
      minZoom: 1,
      maxZoom: 10
});

Before anything can be displayed we also need to let Leaflet know where to retrieve the tiles for the map. If you aren’t familiar with tiles they are the background image that is displayed. These can be road maps, start charts, or any other image. In this example the tiles are pulled from the OpenStreetMap.org repository. We’ll do this by creating a Tile Layer and then add that layer to the map object.

const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 10,
  attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});

tiles.addTo(this.map);

In the HTML for the component there is a button that calls a centerMap() function, passing it latitude and longitude values. When called, this function will pan the map over to the new center coordinates, draw a circle marker on that location, and zoom to a specific level.

centerMap(lat: number, lng: number): void {
this.map.panTo([lat, lng]);
// Generate a circle marker for this location
let currentLocation: L.CircleMarker =
L.circleMarker([lat, lng], {
radius: 5
});
currentLocation.addTo(this.map);
// Wait a short period before zooming to a designated level
setTimeout(() => {this.map.setZoom(8);}, 750);
}

With the definition of the map component completed we need to add the component to the app.component.html file so that we can actually see it in the applicaiton.

<app-map></app-map>

There is one last piece of the puzzle that we need to add in order for the maps to be displayed correctly. We need to reference the Leaflet CSS code in the project’s styles.css file. Without the reference we won’t be able to pull the styles into the project.

@import "~leaflet/dist/leaflet.css";

If you forget this piece you’ll see a partial map appear on the site. A few squares of the map will be visible and if you look in the Console window you won’t see any errors reported. The fact that you missed the styles file isn’t obvious so be sure to include it.

At this point we should be able to run the project and see the map displayed similar to the below image.

$ ng serve

Then when you click on the Locate Philadelphia, PA button it will pan the map over to the city and draw a marker on the city.

If there is a need to take actions when the user changes the zoom level of the map or drags the map to a new location it can be achieved by adding listeners to the "zoomlevelschange" and "moveend" events. In this example we’ll add them during the initialization of the map.

// Initialize the map to display Europe
this.map = L.map('map', {
center: [49.8282, 8.5795],
zoom: 4,
minZoom: 1,
maxZoom: 10
}) // Create a callback for when the user changes the zoom
.addEventListener("zoomlevelschange", this.onZoomChange, this)
// Create a callback for when the map is moved
.addEventListener("moveend", this.onCenterChange, this);

From these callbacks you can grab the new center, zoom level, or map boundaries by accession the map object referenced via the this object. You can see examples of these in the map.component.ts file.

Hopefully this post helps get you on your way adding Leaflet maps to your Angular project. Besides a few behind the scenes updates of files the process is straight forward. In a future post I’ll go over adding markers and custom popup dialogs.

ion-toggle Toggle/Change event handling

I’m working on an Ionic v2+ app that requires some extra logic to be executed when a toggle switch, also known as a radio button, is changed.  In Ionic this component is called an ion-toggle.  I tried using the normal Angular.io decorators like (change) or (click) but none of them were firing.  What I didn’t realize, mainly because I didn’t see it in the Ionic documents was that in order to handle changes in the state I needed to use the (ionChange) decorator on the component.  With this I could pass in more details to the function being executed.

<ion-toggle id="toggle{{parent.name}}{{child.name}}"
     [(ngModel)]="child.selected"
     (ionChange)="onChildSelection(parent.name, child.name, child.selected)">
</ion-toggle>
 In the above example the id of the component is being set to include the name of the parent and child.  The state is based on the child.selected state which in this case is a boolean value.