2

I'm running into an issue that everyone else seems to run into where my ngx-leaflet map is not rendering fully until I resize. I have attempted to call map.invalidateSize() in many ways such as in ngOnInit, onMapReady, all of the above with a Timeout. I get this on the initial load. I am using ngx-leaflet 2.5 because 3.0+ does not work angular 4.4.5.

The button to access the modal is

<app-map
  (closeButtonClicked)="mapModal.hide()"
  class="modal fade"
  bsModal
  #mapModal="bs-modal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="mySmallModalLabel"
  aria-hidden="true"
  [Service]="Service"></app-map>

The css is:

@import '../../app';
#map {
  color: white;
  font: 1em 'Dosis', Arial, sans-serif;
  width: 90vh;
  margin: 30px auto;
  button.close {
    font-size: 30px;
    opacity: .4;
  }
  .modal-dialog {
    width: 100vw;
    height: 50vw;
    .devices-sm({
      width: 90vh;
      height: 70vh;
    });
    .modal-content {
      height: 100%;
      .modal-body {
        height: 100%;
        background-color: @accent-background-color;
        padding: 0;
        #map {
          height: 100%;
          width: 100%;
          padding: 0;
        }
      }
    }
  }
}

This is the code for the actual map

googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
    maxZoom: 20,
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
    detectRetina: true
});
options = {
    layers: [
        this.googleHybrid
    ],
    zoom: 1.49,
    center: L.latLng([180, -180]),

};
reblace
  • 4,115
  • 16
  • 16
wvarner
  • 116
  • 2
  • 7
  • Can you provide some code or more context? Normally, you encounter this issue because you're using some library that's using JS to resize elements on the page or you are using [hidden] to show/hide elements. What's happening is that Leaflet needs you to call "invalidateSize()" after any change to the DOM that affects the size of the existing element containing the map. – reblace Mar 23 '18 at 19:39
  • Just edited. Hope it provides insight. – wvarner Mar 23 '18 at 20:15

2 Answers2

2

Leaflet is very sensitive to the size of the map. This means that any time you show/hide the map, you need to make sure you call the invalidateSize() method. With ngx-bootstrap modals, the right time would be in the onShown event handler.

I've created a branch of the ngx-leaflet-tutorial-ngcli GitHub project [1] that shows how to get this working. While this example is for ng5 and ngx-leaflet@3, the same approach should work for ng4 and ngx-leaflet@2.

[1] https://github.com/Asymmetrik/ngx-leaflet-tutorial-ngcli/compare/develop...ngx-bootstrap-modal-demo

The relevant excerpts are below.

In your component, add a handler for the onShown event:

handleOnShown() {
   this.map.invalidateSize();
}

In your template, set up the (onShown) output to call the handler:

<button type="button" class="btn btn-primary"
        (click)="staticModal.show()">
  Show a Map
</button>

<div class="modal fade"
     bsModal #staticModal="bs-modal"
     [config]="{backdrop: 'static'}"
     tabindex="-1" role="dialog"
     aria-labelledby="mySmallModalLabel" aria-hidden="true"
     (onShown)="handleOnShown()">

  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title pull-left">Static modal</h4>
        <button type="button" class="close pull-right"
                aria-label="Close"
                (click)="staticModal.hide()">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <div class="map"
             leaflet
             (leafletMapReady)="onMapReady($event)"
             [leafletOptions]="options"
             [leafletLayersControl]="layersControl"></div>
      </div>
    </div>
  </div>
</div>
reblace
  • 4,115
  • 16
  • 16
  • This is actually super helpful and I feel like I'm making progress with this. I'm relatively new to angular and I appreciate your help. What if the button is in another component? Should the event fire off anyways? I'm having trouble with that right now. – wvarner Mar 27 '18 at 18:09
  • I would assume that even if the button was in another component, and you were showing/hiding using the modal service, that the (onShown) event will still fire and you should still be able to use the same approach. – reblace Mar 28 '18 at 00:39
  • I'm having issues with picking up events but it could be something with my angular setup. If I set a timeout for onMapReady and pull up the modal before it triggers, everything works. I just have to fix my events not being picked up. Thanks for your help! – wvarner Mar 29 '18 at 16:25
1
/* // In style.css add this class map */
.map {
  position: relative !important;
  width: 100%; height: 600px;
  margin: 2px;
  padding: 2px;
}

/*--------------------------------------------------------------------*/

// In Angular component .ts file

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import * as L from 'leaflet';

@Component({
  selector: 'app-map-control',
  template: 
    `<div class="map" leaflet
      (leafletMapReady)="onMapReady($event)"
      [leafletOptions]="options"
      (leafletMouseOver)="refreshMap()"
      style="border: black solid 1px;">
    </div>`
})

export class MapControlComponent {

  constructor() { }

  public map: L.Map = null;
  private latoLong: L.LatLngTuple = [35.706000, 51.4025000]; // for set static lat-long to map


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

  // Set the initial set of displayed layers (we could also use the leafletLayers input binding for this)
  options = {
    layers: [this.streetMaps],
    zoom: 17,
    center: L.latLng(this.latoLong)
  };

  @Output() outputLatLong = new EventEmitter<L.LatLngTuple>();

  refreshMap() {
    if (this.map) {
      // this.streetMaps.redraw();
      this.map.invalidateSize();
    }
  }

  onMapReady(map: L.Map) {
    map.on('click', (eventMouse: L.LeafletMouseEvent) => {
      this.latoLong = [eventMouse.latlng.lat, eventMouse.latlng.lng];
      map.setView(this.latoLong, map.getZoom());
      this.outputLatLong.emit(this.latoLong);
    });
    this.map = map;
  }

}
  • 1
    Consider adding some context/explanation to your answer. Code alone is often not very helpful. – Viktor Aug 25 '19 at 15:38
  • Please don't add the same answer to multiple questions. Answer the best one and flag the rest as duplicates. See [Is it acceptable to add a duplicate answer to several questions?](//meta.stackexchange.com/q/104227) – Dharman Aug 25 '19 at 16:13