Getting Started with ngx-leaflet
Add Maps to Angular CLI Projects
Nov 21, 2017

In our last post, we introduced @asymmetrik/ngx-leaflet – a compact and extensible library for integrating Leaflet maps into Angular.io applications. Now, we’ll show you how you can integrate it into your @angular/cli projects. This tutorial now supports Angular v7.

Getting and Running this Tutorial

If you just want to see a working example, you can find the code for this tutorial on Github. All you need to do is clone the repo, install the dependencies, and run the application using the following commands:

# Clone the Repository
$ git clone git@github.com:Asymmetrik/ngx-leaflet-tutorial-ngcli.git

# Install @angular/cli
$ npm install -g @angular/cli

# Install the dependencies with npm
$ cd ngx-leaflet-tutorial-ngcli
$ npm install

# Run the tutorial application
$ ng serve

For a complete walkthrough, read on! The following sections walk you through installing the ngx-leaflet plugin, creating a simple map, and adding customer layers and markers.

Using ngx-leaflet with Angular CLI

The following walkthrough demonstrates how to create a new project, import and configure the @asymmetrik/ngx-leaflet plugin, and use the plugin to add maps. As previously mentioned, you can find the code for this tutorial on Github.

Step 1: Creating the @angular/cli Project

The first step is to install @angular/cli and create a new project.

# Install @angular/cli
$ npm install -g @angular/cli

# Create a new project
$ ng new ngx-leaflet-tutorial-ngcli
$ cd ngx-leaflet-tutorial-ngcli

Once you’ve created the project, run it using ng serve.

$ ng serve

To see the running site, hit the url: http://localhost:4200. You should see the welcome page:

screenshot of the @angular/cli welcome page

As you make changes to the code, @angular/cli rebuilds and redeploys the code automagically. If it gets out of sync, just kill the process and rerun ng serve. You can read more about @angular/cli on their Github page.

Step 2: Install and configure the dependencies

@asymmetrik/ngx-leaflet depends on Leaflet. Also, you’ll need to import the TypeScript Declaration File for Leaflet since @angular/cli leverages TypeScript.

# From inside the project directory, add the dependencies using npm
$ npm install leaflet@1
$ npm install @types/leaflet@1
$ npm install @asymmetrik/ngx-leaflet@5

Note: @asymmetrik/ngx-leaflet@5 supports Angular.io v7. To use earlier versions of Angular.io, check the Changelog section of the README.

Once all the dependencies are installed, the next step is to add the ngx-leaflet module to the Angular project. To do this, add the LeafletModule to AppModule in src/app/app.module.ts as shown below:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    LeafletModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

For the map to render correctly, you have to add the Leaflet stylesheet to the Angular CLI global stylesheet configuration. To do this, add the following to angular.json:

{
  ...
  "styles": [
    "src/styles.css",
      "./node_modules/leaflet/dist/leaflet.css"
    ],
  ...
}

And, if you want markers to show up correctly on your maps, you need to configure Angular CLI to expose the Leaflet assets to your application. To do this, make the following additional changes to angular.json:

{
  ...
  "assets": [
    {
      "glob": "**/*",
      "input": "./node_modules/leaflet/dist/images",
      "output": "leaflet/"
    },
    "src/assets",
    "src/favicon.ico"
  ],
  ...
}

Step 3: Create a Map!

Now that all that setup is out of the way, you can get to the fun part. The next step is to add a map to the application. First, replace the contents of src/app/app.component.html with a single div containing the leaflet directive:

<div class="map"
     leaflet
     [leafletOptions]="options">
</div>

Next, define the component to go with the template. In order to create a map, you need to provide a center point and an initial zoom level. And, since the map is pretty boring without any base layers, go ahead and provide one of those too. In src/app/app.component.ts:

import { Component } from '@angular/core';
import { latLng, tileLayer } from 'leaflet';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  options = {
    layers: [
      tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; OpenStreetMap contributors'
      })
    ],
    zoom: 7,
    center: latLng([ 46.879966, -121.726909 ])
  };

}

The last thing you need to do to get this all working is add some styles to make sure the map expands to fit the whole screen. It’s actually pretty simple, but you need to modify two different stylesheets because some of the styles are local to the component and some are global to the application.

First, style the app component so it expands to fit its entire container by adding the following to src/app/app.component.css:


.map {
  height: 100%;
  padding: 0;
}

Second, add a global style to make the html and body elements full screen by modifying src/styles.css:

/* You can add global styles to this file, and also import other style files */

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}

At this point, you should be able to run the application. Launch it with the ng serve command. Then, navigate to http://localhost:4200. You should see a map. Exciting, right?

screenshot of a full screen Leaflet map running in an Angular.io application

Step 4: Adding stuff to the map

Once you’ve got a map showing up in your application, you can start to do useful things with it. For this tutorial, you are going to display a popular Mt. Ranier climbing route on the map using a polyline and two markers. And, you’re going to add a layers control so users can turn layers on and off and switch between map and satellite baselayers.

In src/app/app.component.ts, create the new layers and the layers control object:

import { Component } from '@angular/core';

import { icon, latLng, marker, polyline, tileLayer } from 'leaflet';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {


  // Define our base layers so we can reference them multiple times
  streetMaps = tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    detectRetina: true,
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  });
  wMaps = tileLayer('http://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png', {
    detectRetina: true,
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  });

  // Marker for the top of Mt. Ranier
  summit = marker([ 46.8523, -121.7603 ], {
    icon: icon({
      iconSize: [ 25, 41 ],
      iconAnchor: [ 13, 41 ],
      iconUrl: 'leaflet/marker-icon.png',
      shadowUrl: 'leaflet/marker-shadow.png'
    })
  });

  // Marker for the parking lot at the base of Mt. Ranier trails
  paradise = marker([ 46.78465227596462,-121.74141269177198 ], {
    icon: icon({
      iconSize: [ 25, 41 ],
      iconAnchor: [ 13, 41 ],
      iconUrl: 'leaflet/marker-icon.png',
      shadowUrl: 'leaflet/marker-shadow.png'
    })
  });

  // Path from paradise to summit - most points omitted from this example for brevity
  route = polyline([[ 46.78465227596462,-121.74141269177198 ],
    [ 46.80047278292477, -121.73470708541572 ],
    [ 46.815471360459924, -121.72521826811135 ],
    [ 46.8360239546746, -121.7323131300509 ],
    [ 46.844306448474526, -121.73327445052564 ],
    [ 46.84979408048093, -121.74325201660395 ],
    [ 46.853193528950214, -121.74823296256363 ],
    [ 46.85322881676257, -121.74843915738165 ],
    [ 46.85119913890958, -121.7519719619304 ],
    [ 46.85103829018772, -121.7542376741767 ],
    [ 46.85101557523012, -121.75431755371392 ],
    [ 46.85140013694763, -121.75727385096252 ],
    [ 46.8525277543813, -121.75995212048292 ],
    [ 46.85290292836726, -121.76049157977104 ],
    [ 46.8528160918504, -121.76042997278273 ]]);

  // Layers control object with our two base layers and the three overlay layers
  layersControl = {
    baseLayers: {
      'Street Maps': this.streetMaps,
      'Wikimedia Maps': this.wMaps
    },
    overlays: {
      'Mt. Rainier Summit': this.summit,
      'Mt. Rainier Paradise Start': this.paradise,
      'Mt. Rainier Climb Route': this.route
    }
  };


  // Set the initial set of displayed layers (we could also use the leafletLayers input binding for this)
  options = {
    layers: [ this.streetMaps, this.route, this.summit, this.paradise ],
    zoom: 7,
    center: latLng([ 46.879966, -121.726909 ])
  };

}

In src/app/app.component.html, add the binding for the layers control:

<div class="map"
  leaflet
  [leafletOptions]="options"
  [leafletLayersControl]="layersControl"></div>

At this point, you should see a path and a couple of markers on the map.

screenshot of a full screen Leaflet map displaying markers and a polyline

So, that’s super awesome, and other than the boilerplate setup, it wasn’t that difficult. But, what if you want to zoom and pan so that the user can actually see the route and the markers? It’s actually pretty easy using the fitBounds function in leaflet. The next section demonstrates how.

Step 5: Working with the map

The ngx-leaflet plugin exposes some commonly used functionality (like fitBounds) through Angular input bindings. Alternatively, ngx-leaflet exposes a reference to the map instance using the leafletMapReady output binding. This way, you can do whatever you need to do to the map directly. Since the latter is more flexible, you should learn to use it first. All you need to do is define a callback function in src/app/app.component.ts:

import { Component } from '@angular/core';

import { icon, latLng, Map, marker, point, polyline, tileLayer } from 'leaflet';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  ...

  onMapReady(map: Map) {
    map.fitBounds(this.route.getBounds(), {
      padding: point(24, 24),
      maxZoom: 12,
      animate: true
    });
  }

}

Next, add the leafletMapReady output binding to src/app/app.component.html:

<div class="map"
     leaflet
     (leafletMapReady)="onMapReady($event)"
     [leafletOptions]="options"
     [leafletLayersControl]="layersControl"></div>

When the map is created, the ngx-leaflet directive calls onMapReady passing a reference to the map as an argument. The function used map.fitBounds(...) to zoom and center the map around a bounding box derived from the path. The result is a map that shows a popular climbing route from the Paradise parking area to the summit of Mt. Ranier.

screenshot of a full screen Leaflet map displaying markers and a polyline zoomed and centered on the polyline

Learn More

This tutorial walked you through a lot of the basic functionality of the @asymmetrik/ngx-leaflet plugin. At Asymmetrik, we’re working on more tutorials that show off some of the advanced functionality of ngx-leaflet, including how to extend the plugin, how to integrate with other bundling frameworks, and how to do some more advanced map interaction and configuration. In the interim, feel free to ask questions or report issues on the ngx-leaflet Github project page.