Unleashing Location Visualization: Implementing Leaflet JavaScript Library to Display Interactive Maps (Part 5) in Angular-15 Ionic-7 App
Welcome to part 5 of my series on building my first Angular-15 Ionic-7 app. In this post, I will show you how to use the Leaflet JavaScript library to display a single location map on your Ionic app. The leaflet is a popular open-source library for interactive maps that work well with Ionic and Angular. You will learn how to install and configure Leaflet, how to create a map component, and how to add a marker and a popup to the map. By the end of this post, you will have a basic understanding of how to use Leaflet in your Ionic app and how to customize it to your needs.
Since individual modules are provided with latitude and longitude values with the location name of the module, I will first create a bottom sheet style pop-up for ever module.
The tab1-details.page.html
page currently displays only the static values of the location. I will add a button that will open a map modal for every module page like this:
...
<ion-content [fullscreen]="true">
...
<ion-card *ngIf="module">
...
</ion-card>
<ion-card *ngIf="module">
<ion-card-header>
<ion-card-title>Location</ion-card-title>
<ion-card-subtitle>{{module.moduleName}}</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
...
<ion-item>
<ion-label>Latitude: {{module.lat}}</ion-label>
</ion-item>
<ion-item>
<ion-label>Longitude: {{module.long}}</ion-label>
</ion-item>
<ion-button
(click)="openMap(module)"
expand="full"
fill="solid"
shape="round"
>
View on Map
</ion-button>
</ion-list>
</ion-card-content>
</ion-card>
</ion-content>
The click event listener will call the method openMap(module)
which will take in the module object.
Next, I will create a new page that will behave as the modal page view for the map using the command:
ionic generate page pages/map
Then I transfer the map’s route from app.route.ts
to tabs.route.ts
with this:
{
path: 'map',
loadComponent: () => import('../map/map.page').then((m) => m.MapPage),
},
It is also important to import the map page in the app.component.ts
file and put it into the imports array there to make the modal appear on top of the other pages.
Now, I will create the logic for this method in the tab1-details.page.ts
page in the following way:
...
import { ActivatedRoute, RouterLink } from '@angular/router';
import {
ModuleServiceService,
Module,
} from 'src/app/services/module-service.service';
import { ModalController } from '@ionic/angular';
import { MapPage } from '../map/map.page';
@Component({
selector: 'app-tab1-details',
templateUrl: './tab1-details.page.html',
styleUrls: ['./tab1-details.page.scss'],
standalone: true,
imports: [IonicModule, CommonModule, FormsModule, RouterLink, MapPage],
})
export class Tab1DetailsPage implements OnInit {
...
constructor(
private activatedRoute: ActivatedRoute, // used to get the moduleNo from the url
private moduleService: ModuleServiceService,
private modalCtrl: ModalController
) {}
ngOnInit() {
...
}
...
// create a method to open the map
async openMap(module: any) {
//open the map in a modal
const modal = await this.modalCtrl.create({
// create a modal
component: MapPage, // set the modal component
componentProps: {
moduleId: module.moduleNo,
moduleLat: module.lat,
moduleLong: module.long,
moduleLocation: module.location,
moduleRoom: module.room,
}, // set the modal component properties
breakpoints: [0, 0.75, 1], // set the breakpoints
initialBreakpoint: 0.75, // set the initial breakpoint
}); // create a modal
modal.present(); // present the modal
}
}
The above code creates an asynchronous method called openMap
that takes in a parameter called module
. Within the openMap
method, a modal is created using the create
method of the modalCtrl
object. The component
property of the modal is set to MapPage
, which is presumably a page/component that displays a map. The componentProps
property is an object that contains properties of the module
parameter. These properties include the moduleNo
, lat
, long
, location
, and room
of the module
object. These properties are then passed to the MapPage
component. The breakpoints
property is an array that specifies the breakpoints for the modal. The initialBreakpoint
property specifies the initial breakpoint for the modal. Once the modal is created, the present
method is called to present the modal to the user.
To begin working with maps, I tried out multiple providers, and found that Leaflet JS library provides a quick setup at no cost. I referenced the following for this:
Leaflet — an open-source JavaScript library for interactive maps
I entered the following command to install the leaflet library:
npm install leaflet
The leaflet library will appear in the package.json
file along with other dependencies. Next, I begin to write the logic to access the map, markers and tile layers from the leaflet documentation in the map.page.ts
file in the following way:
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Input } from '@angular/core';
import * as L from 'leaflet';
import { Module } from 'src/app/services/module-service.service';
@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
standalone: true,
imports: [IonicModule, CommonModule, FormsModule],
})
export class MapPage implements OnInit {
module!: Module; // module object
@Input() moduleId!: string; // id of the module taken from the modal component props
@Input() moduleLat!: number; // latitude of the module taken from the modal component props
@Input() moduleLong!: number; // longitude of the module taken from the modal component props
@Input() moduleLocation!: string; // location of the module taken from the modal component props
@Input() moduleRoom!: string; // room of the module taken from the modal component props
map!: L.Map;
constructor() {}
ngOnInit() {}
ionViewDidEnter() {
// this is a lifecycle hook that is called when the view is loaded into the DOM and ready to be presented to the user
// create a spinner to show while the map is loading
const spinner = document.createElement('ion-spinner');
spinner.name = 'crescent';
spinner.style.cssText = 'position: absolute; top: 10%; left: 50%;';
document.getElementById('map')?.appendChild(spinner);
setTimeout(() => {
// timeout to wait for the modal to load and for the div id called map to be created in the html otherwise the the error appears that 'el' property is undefined
this.map = L.map('map').setView([this.moduleLat, this.moduleLong], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'Map data © <a href="<https://www.openstreetmap.org/>">OpenStreetMap</a> contributors',
}).addTo(this.map);
const blueIcon = L.icon({
iconUrl: 'assets/images/marker-icon-2x.png', //remember to shift the images folder from the node_modules/leaflet/dist/images to the src/assets/images folder otherwise the images will not be found.
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowUrl: 'assets/images/marker-shadow.png',
shadowSize: [41, 41],
shadowAnchor: [12, 41],
});
// add a marker to the map
L.marker([this.moduleLat, this.moduleLong], { icon: blueIcon })
.addTo(this.map)
.bindPopup(this.moduleLocation + ' | Room No. ' + this.moduleRoom)
.openPopup();
// add a circle to the map
L.circle([this.moduleLat, this.moduleLong], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 50,
}).addTo(this.map);
}, 500);
}
}
The above code defines a MapPage
class that implements the OnInit
interface. The class contains several input properties (moduleId
, moduleLat
, moduleLong
, moduleLocation
, and moduleRoom
) that are used to display a map. In the ionViewDidEnter
lifecycle hook, a spinner is created and added to the DOM. After a timeout of 500 milliseconds, the map is initialized using the Leaflet.js library and displayed on the page. The setView
method sets the initial map view to the latitude and longitude specified in the input properties. A tile layer from OpenStreetMap is added to the map using the tileLayer
method. A blue marker is added to the map using the marker
method, which represents the location of the module. A circle is also added to the map using the circle
method with a red border, a fill color of #f03
, and a radius of 50. Finally, the location and room of the module are displayed in a popup that is bound to the marker using the bindPopup
method, and the popup is opened using the openPopup
method.
Finally, the logic has to be implemented on the template file of map.page.html
in the following way:
<ion-content [fullscreen]="true">
<div class="map-container">
<div class="map-frame">
<div class="map" id="map"></div>
</div>
</div>
</ion-content>
Along with the above template, I have added some styling to the modal page for a better UI in the map.page.scss
file like this:
.map-container {
height: 100%;
width: 100%;
}
.map-frame {
height: 100%;
}
#map {
height: 100%;
margin-top: 20px;
}
I have referred to the following documentation to get this library up and running: Quick Start Guide - Leaflet - a JavaScript library for interactive maps (leafletjs.com). In Figure 16, the module detail page shows the button to open the map, and in Figure 17, the modal appears as a bottom sheet after clicking on the ‘View on Map’ button:
Figure 17: Map button on individual module details page to open map modal |
Figure 18: Map modal appears with marker, circle, popup label and zoom buttons. |