2

I want to display a ngx-leaflet map inside a ng-bootstrap modal, centered in a determinate point. The map is encapsulated in a component like this:

HTML

<div class="modal-header">
  <h4 class="modal-title">Map</h4>
  <button type="button" class="close" aria-label="Close" 
      (click)="activeModal.dismiss('Cross click')">
      <span aria-hidden="true">&times;</span>
  </button>
</div>

<div class="modal-body">
  <div id="map" 
    leaflet
    [leafletOptions]="options"
    [leafletLayersControl]="layersControl"></div>
</div>

<div class="modal-footer">
  <button type="button" class="btn btn-outline-dark" (click)="activeModal.close('Close click')">Close</button>
</div>

TS

 constructor(public activeModal: NgbActiveModal) { }

  ngOnInit() {
  }

  streetMap = L.tileLayer('http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', {
    maxZoom: 20,
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
  });

  hybridMap = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
    maxZoom: 20,
    subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
  });

  vehicleMarker = L.marker([40.4168, -3.703790], {
    icon: L.icon({
      iconSize: [25, 41],
      iconAnchor: [13, 41],
      iconUrl: 'assets/marker-icon.png',
      shadowUrl: 'assets/marker-shadow.png'
    })
  });

  layersControl = {
    baseLayers: {
      'Map view': this.streetMap,
      'Map hybrid': this.hybridMap
    },
    overlays: {
      'Vehicle': this.vehicleMarker
    }
  };

  options = {
    layers: [this.streetMap, this.vehicleMarker],
    zoom: 5,
    center: L.latLng([40.4168, -3.703790])
  };

And in another component I open the modal like this:

constructor(private modalService: NgbModal) { }

ngOnInit() {
}

openMap() {
    this.modalService.open(MapComponent);
}

Everything works fine outside the modal (the map is centered in the given point) but when I render the map inside the modal the map is not centered. How can I solve this issue?

I realice that in small screens (like Galaxy S5 360x460) the map is displayed fine (centered).

Juan
  • 2,024
  • 3
  • 17
  • 36

2 Answers2

3

Leaflet is very sensitive to changes to page layout and sizing. Generally, when the page layout changes you need to call Map.invalidateSize().

ngx-leaflet automatically tracks window resize events and calls invalidateSize() for you, so you don't usually have to worry about it. But, when you're using something like Bootstrap or JQuery to manipulate the page layout independently of resizing the window, you may need to manually call it.

First, grab a reference to the Map using the instructions in the README: https://github.com/Asymmetrik/ngx-leaflet#getting-a-reference-to-the-map

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

map: Map;
onMapReady(map: Map) {
   this.map = map;
}

Then, add a call to this.map.invalidateSize() whenever you open/show/resize the map modal.

If this doesn't help, please provide an angular CLI project on GitHub that reproduces the problem and I can take a closer look.

reblace
  • 4,115
  • 16
  • 16
  • In order to achieve this I need to share the map reference between two sibling components. Which is the best way to accomplish that, using a service? – Juan Mar 06 '18 at 08:36
  • I used a service to share the map reference but the invalidateSize() call doesn't work. – Juan Mar 06 '18 at 09:20
  • Can you provide a more complete example that reproduces the problem? Something I can check out and run? A quick test is to resize the window when you see it looks like it's not working and see if it fixes it. Resizing the window forces an internal call to invalidateSize() – reblace Mar 07 '18 at 18:23
1

I'm Parham, for fix this problem issue in Angular 6 and bootstrap Modal use this instruction. Today fixed my problem with this instruction. To correct this problem issue should be use method this.map.invalidateSize(); in event (leafletMouseOver). Please see this sample code from here down.

    /* // 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;
  }

}

From using in your component application:

<app-map-control (outputLatLong)="getLatLong($event)"></app-map-control>
  • 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