'Can't load Leaflet inside Vue component

I am trying to create a Leaflet map as a Vue component but I am having some difficult getting started. I installed Leaflet through npm

Where am I going wrong? console.log(Leaflet) is returning a Leaflet object but I am having trouble getting the map to expand and render.

Some direction would be appreciated

<template>
    <div id="map"></div>
</template>

<script>
    // import leaflet here?
    import Leaflet from 'leaflet';

    export default {
        components: {
            Leaflet
        },

        created() {
            console.log(this);
            console.log(Leaflet);
        },

        ready() {
            this.map = L.map('map').setView([51.959, -8.623], 14);
            L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
                attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            }).addTo(this.map);
        }
    }
</script>

<style>
    #map {
        height: 100%;
        width: 100%;
        margin-top: -24px;
    }

    /* default legend, layer styling from leaflet template */
    .info {
        padding: 6px 8px;
        font: 14px/16px Arial, Helvetica, sans-serif;
        background: white;
        background: rgba(255,255,255,0.8);
        box-shadow: 0 0 15px rgba(0,0,0,0.2);
        border-radius: 5px;
    }
    .info h4 {
        margin: 0 0 5px;
        color: #777;
    }
    .legend {
        text-align: left;
        line-height: 18px;
        color: #555;
    }
    .legend i {
        width: 18px;
        height: 18px;
        float: left;
        margin-right: 8px;
        opacity: 0.7;
    }
</style>


Solution 1:[1]

There are a couple problems in your code:

  1. There is not a ready lifecycle hook in Vue. Use mounted().
  2. You are trying to pass the Leaflet library as a component to Vue, which does nothing since Leaflet is not a Vue component and does not need any sort of registering with Vue.
  3. A map data property was not declared on the Vue component.
  4. There is no problem with importing the library as Leaflet from 'leaflet', but it might be more consistent for yourself or others to declare the Leaflet object as L from 'leaflet'. You can also use: import { Map } from 'leaflet', but then must initialize your Map appropriately: this.map = new Map("mapContainer")
  5. Prevent potential memory leaks and/or cleanup appropriately by using the remove() method on a Leaflet Map Class. A good spot is within the Vue beforeDestroy lifecycle hook.

Also, don't forget to import the Leaflet CSS, for example: import "leaflet/dist/leaflet.css";

<template>
  <div id="mapContainer"></div>
</template>

<script>
import "leaflet/dist/leaflet.css";
import L from "leaflet";

export default {
  name: "LeafletMap",
  data() {
    return {
      map: null
    };
  },
  mounted() {
    this.map = L.map("mapContainer").setView([51.959, -8.623], 12);
    L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
      attribution:
        '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(this.map);
  },
  beforeDestroy() {
    if (this.map) {
      this.map.remove();
    }
  }
};
</script>

<style scoped>
#mapContainer {
  width: 100vw;
  height: 100vh;
}
</style>

Solution 2:[2]

The correct answer was already provided by jhickok but was insightful for the vue3 composition API as well. The leaflet map can't be instantiated in setup() for the same reasons explained.

<template>
  <div id="mapContainer"></div>
</template>

<script>
import { map, tileLayer } from "leaflet";
import { onMounted, onBeforeUnmount } from "vue";

export default {
  name: "Map",
  setup() {
    let container = null;

    onMounted(() => {
      container = map("mapContainer").setView([51.959, -8.623], 12);

      tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
        attribution:
          '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      }).addTo(container);
    });

    onBeforeUnmount(() => {
      container.remove();
    });
  }
};
</script>

<style scoped>
#mapContainer {
  width: 40vw;
  height: 40vh;
}
</style>

Solution 3:[3]

first install leafletjs and vue2-leaflet with npm

second copy the code to your component

<template>
      <div id="app" v-if="isLoad">
        <l-map style="height: 350px" :zoom="zoom"   :center="center" @click="addMarker"   :max-zoom="maxZoom" :min-zoom="minZoom">
          <l-tile-layer :url="url" :attribution="attribution"></l-tilelayer>
          <l-marker  :lat-lng="markerLatLng" ></l-marker>
        </l-map>
      </div>
</template>

<script>
    import {LMap, LTileLayer, LMarker} from "vue2-leaflet";
    import "leaflet/dist/leaflet.css";
    import {Icon} from "leaflet";
    
    delete Icon.Default.prototype._getIconUrl;
    
    Icon.Default.mergeOptions({
      iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
      iconUrl: require("leaflet/dist/images/marker-icon.png"),
      shadowUrl: require("leaflet/dist/images/marker-shadow.png")
    });
    export default {
      components: {
        LMap,
        LTileLayer,
        LMarker,
      },
    
      mounted() {
        this.mapIsReady()
      },
      data() {
        return {
          isLoad: false,
          url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
          attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
           zoom: 15,
          maxZoom:18,
          minZoom:5,
          center: [35.6892, 51.3890],
          markerLatLng: [35.6892, 51.3890],
        };
      },
      methods: {
        mapIsReady() {
          let self = this
          setTimeout(() => self.isLoad = true, 1500)
        },
        addMarker(event) {
          console.log(event)
          this.markerLatLng = [event.latlng.lat,event.latlng.lng];
        },
    
      },
    };
</script>

this code has add marker , max zoom and min zoom for your map

this code used open street map because it's free. you can change it to api.mapbox.com if you want but you need api key or accessToken

Solution 4:[4]

I just did this, but I needed basemaps too (bing).

leafletWrapper.js

const L = require('leaflet')
require('leaflet-bing-layer')

export default L

LeafletMap.vue

<template>
  <div id="map">
  </div>
</template>

<script>
import L from 'leafletWrapper'

export default {
    name: 'leaflet-map',
    props: ['center', 'zoom', 'minZoom', 'maxZoom', 'markers'],
    data () {
      return {
        mapObject: {},
        markerLayer: {}
      }
    },
 mounted () {
      const bingKey = 'foo'

      this.mapObject = L.map(
        "map", {
          center: this.center,
          zoom: this.zoom,
          minZoom: this.minZoom,
          maxZoom: this.maxZoom
        }
      )

      L.tileLayer.bing({bingMapsKey: bingKey, imagerySet: 'Road'}).addTo(this.mapObject)

    },
    beforeDestroy () {
      this.mapObject.clearAllEventListeners()
    }

  }
</script>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 evendiagram
Solution 3
Solution 4 Jon West