Building Maps in Angular using Leaflet, Part 4: The Shape Service

Chris Engelsma

By now, we’ve built a Leaflet map in Angular and we are able to render markers and pop-ups. Let’s now render shapes for the different US states.

For this exercise, I downloaded a GeoJSON file that contains shape outlines of each state. Eric Celeste has an awesome site chock full of map data. I grabbed a 5m-GeoJSON states-outline from his site for this demo. Put this in your /assets/data folder.

Note: If your local ng serve is already running, you'll need to shut it down and restart in order to reload the new assets.

Setup

Our directory structure should look like this:

leaflet-example
|_ node_modules/
|_ package.json
\_ src/
    \_ app/
        |_ app.module.ts
        |_ app.routing.ts
        |_ app.component.ts
        |_ app.component.html
        |_ app.component.scss
        |
        |_ map/
        |     |_ map.component.ts
        |     |_ map.component.html
        |     \_ map.component.scss
        |
        \_ _services/
              |_ marker.service.ts
              |_ pop-up.service.ts

Let’s create another service that will be responsible for loading our shapes.

$ ng generate service shape

Add this new service as a provider in your app.module.ts.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { MapComponent } from './map/map.component';
import { MarkerService } from './_services/marker.service';
import { HttpClientModule } from '@angular/common/http';
import { PopUpService } from './_services/pop-up.service';
import { ShapeService } from './_services/shape.service';

@NgModule({
  declarations: [
    AppComponent,
    MapComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
  ],
  providers: [
    MarkerService,
    PopUpService,
    ShapeService
  ],
  bootstrap: [AppComponent]
})
export class AppModule {} 

🐊 Alligator.io recommends

Our recommended Angular courses

Loading the Shapes

In the ShapeService, we’ll only add a single function for now that will load the geojson file from our assets.

shape.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ShapeService {

  constructor(private http: HttpClient) { }

  getStateShapes(): Observable<any> {
  return this.http.get('/assets/data/usa-states.geojson');
}
}

Our function getStateShapes() will return an observable of our serialized geojson object. To use this, we need to subscribe to the observable in our MapComponent. Here’s how:

  1. Inject the ShapeService in the constructor.
  2. Create a local variable to house the data.
  3. Call the shape service function to pull the data and subscribe to the result.

map.component.ts

export class MapComponent implements AfterViewInit {

  private states;

  constructor(private markerService: MarkerService,
              private shapeService: ShapeService) { }

  ngAfterViewInit(): void {
    this.initMap();
    this.markerService.makeCapitalCircleMarkers(this.map);
    this.shapeService.getStateShapes().subscribe(states => {
  this.states = states;
});
  }

    // ... The rest of the class

}

Note: An even better approach would be to pre-load the data in a resolver.

Once the data are loaded, we’ll need to add the shapes to the map as a layer. Leaflet provides a factory just for geojson layers that we can leverage. Let’s stick this logic in its own function and then call it after the data has been resolved.

map.component.ts

// ...

  ngAfterViewInit(): void {
    this.initMap();
    this.markerService.makeCapitalCircleMarkers(this.map);
    this.shapeService.getStateShapes().subscribe(states => {
      this.states = states;
      this.initStatesLayer();
    });
  }

  private initStatesLayer() {
    const stateLayer = L.geoJSON(this.states, {
      style: (feature) => ({
        weight: 3,
        opacity: 0.5,
        color: '#008f68',
        fillOpacity: 0.8,
        fillColor: '#6DB65B'
      })
    });

    this.map.addLayer(stateLayer);
  }
  
  // ...

I created a new function, initStatesLayer(), that creates a new geojson layer and adds it to the map. I’ve also thrown in some basic styling. Refresh your browser.

Some green looking states

Ta-da! 🎉 We can now see the states - but we can also add a bit of code to interact with them.

For this example, I’m going to have the feature highlight yellow when I hover my cursor over it, and reset back when I leave.

To do this, first define what to do on each feature using the appropriately-named onEachFeature property:

map.component.ts

private initStatesLayer() {
    const stateLayer = L.geoJSON(this.states, {
      style: (feature) => ({
        weight: 3,
        opacity: 0.5,
        color: '#008f68',
        fillOpacity: 0.8,
        fillColor: '#6DB65B'
      }),
      onEachFeature: (feature, layer) => (
  layer.on({
    mouseover: (e) => (this.highlightFeature(e)),
    mouseout: (e) => (this.resetFeature(e)),
  })
)
    });

    this.map.addLayer(stateLayer);
  }
  
  private highlightFeature(e)  {
    const layer = e.target;
    layer.setStyle({
      weight: 10,
      opacity: 1.0,
      color: '#DFA612',
      fillOpacity: 1.0,
      fillColor: '#FAE042',
    });
  }

  private resetFeature(e)  {
    const layer = e.target;
    layer.setStyle({
      weight: 3,
      opacity: 0.5,
      color: '#008f68',
      fillOpacity: 0.8,
      fillColor: '#6DB65B'
    });
  }

  

Reload your browser and hover your cursor across the map:

See if you can spot Texas in this map

Hold on… where did our markers go? They’re still there, we’re just rendering the states on top of them because objects in the map are rendered in the order that they’re called. Because we create the markers first and then the shapes, the shapes win.

We can fix this in one of two ways:

  1. Move the marker call after the shape creation.
  2. Calling stateLayer.bringToBack() on the shape layer after we add it to the map.

In either case, we can now see the markers:

The gang's all here

Looks good!

Happy Mapping

Additional Resources

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#