Search code examples
angulartypescriptleafletproperty-bindingngx-leaflet

Property binding map click event with dropdown select input in ngx-leaflet/Angular 2


I have a map with regional polygons. Below the map, I have a dropdown select input that dynamically reads in the polygon names as options. When the user clicks the regional polygons on the map, I want the dropdown select input to update with the selected polygon name (which exists as the 'name' field in the geojson file).

I figure that the way to achieve this is to use property binding in the HTML template file. So in my select class, I set the value property as the value clicked, like so:

<select class="form-control" id="selectRegion" [value]="clicked">

clicked is initially defined as an empty string in the app component. When a polygon is clicked, I set clicked as the polygon name field inside the onEachFeature click event. The console log shows that the clicked variable is updating properly. However, the select input does NOT update with the click events as expected.

I suspect that the issue is that this isn't properly updating inside my functions? How do I get the select input to update with my map click events?

Here's some code (based on ngx-leaflet-tutorial-ngcli from Asymmetrik):

Geojson (saved as polygons.geojson in the assets folder:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "stroke": "#555555",
        "stroke-width": 2,
        "stroke-opacity": 1,
        "fill": "#555555",
        "fill-opacity": 0.5,
        "name": "poly1"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -121.95098876953125,
              46.82966386051541
            ],
            [
              -121.78482055664061,
              46.82966386051541
            ],
            [
              -121.78482055664061,
              46.91368905872705
            ],
            [
              -121.95098876953125,
              46.91368905872705
            ],
            [
              -121.95098876953125,
              46.82966386051541
            ]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "stroke": "#555555",
        "stroke-width": 2,
        "stroke-opacity": 1,
        "fill": "#555555",
        "fill-opacity": 0.5,
        "name": "poly2"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -121.77726745605469,
              46.83107318799318
            ],
            [
              -121.62963867187499,
              46.83107318799318
            ],
            [
              -121.62963867187499,
              46.913220009605624
            ],
            [
              -121.77726745605469,
              46.913220009605624
            ],
            [
              -121.77726745605469,
              46.83107318799318
            ]
          ]
        ]
      }
    }
  ]
}

App.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
import { HttpClientModule } from '@angular/common/http';


import { AppComponent } from './app.component';

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

App.component.html:

<div class="map"
  leaflet
  [leafletLayers]="layers"
     [leafletFitBounds]="fitBounds"></div>
<div class="form-group">
  <select class="form-control" id="selectRegion" [value] = "clicked">
    <option *ngFor="let region of regions">{{ region }}</option>
  </select>
</div>

App.component.ts:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import * as L from 'leaflet';

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

  layers: L.Layer[];
  fitBounds = [[46.67, -122.25], [47.01, -121.302]];
  regions = [];
  clicked = '';

  constructor(private http: HttpClient) { }

  ngOnInit() {

    // read in geojson of poly
    this.http.get<any>('/assets/polygons.geojson')
      .subscribe(poly => {

        const tileLayer = L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png', {
          subdomains: 'abcd',
          maxZoom: 19
        });

        const polyLayer = L.geoJSON(poly, {
          onEachFeature: this.onEachFeature.bind(this)
        });

        this.layers = [ tileLayer, polyLayer ];
        console.log(this);
      });
  }

  // loop through each feature of polyLayer
  onEachFeature(feature, layer) {

    // push polygon names to regions array
    this.regions.push(feature.properties.name);

    layer.on('click', <LeafletMouseEvent> (e) => {
      this.clicked = e.target.feature.properties.name;
      console.log(this.clicked);
    });
  }
}

Solution

  • The onEachFeature function and layer.on('click'... event callback are likely being invoked outside of the Angular zone. If this is the case, then change detection won't automatically work. You should wrap the changes in zone.run() calls in order to ensure that your changes are being made in the Angular zone - as in this example:

    // loop through each feature of polyLayer
    onEachFeature(feature, layer) {
    
      this.zone.run(() => {
        // push polygon names to regions array
        this.regions.push(feature.properties.name);
    
        layer.on('click', <LeafletMouseEvent> (e) => {
          this.zone.run(() => {
            this.clicked = e.target.feature.properties.name;
            console.log(this.clicked);
          }
        });
      }
    
    }
    

    You can follow these directions (https://github.com/Asymmetrik/ngx-leaflet#a-note-about-change-detection) to see how this works and to inject the ngZone instance into your component.