I am currently working on an application using LitElement components. I would like to integrate Leaflet into it and am having issues with displaying the map. I have installed Leaflet in my project with npm and have created a class looking like this.
import {LitElement, html, css} from 'lit-element';
import './node_modules/leaflet/dist/leaflet';
class Map extends LitElement{
static get styles() {
return [css``];
}
constructor() {
super();
}
connectedCallback() {
super.connectedCallback();
let map = L.map('mapid').setView([51.505, -0.09], 13);
let urlTemplate = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
map.addLayer(L.tileLayer(urlTemplate, {minZoom: 4}));
}
render() {
return html`
<link rel="stylesheet" href="./node_modules/leaflet/dist/leaflet.css">
<div id="mapid" style="height: 100%"></div>
`;
}
}
customElements.define("my-map", Map);
Running my application results in following error: Uncaught TypeError: Cannot set property 'L' of undefined. I am a little lost on how I could use Leaflet to display a map in my LitElement application and would be thankful for a nudge into the right direction.
for Lit projects we tend to suggest people stick to using the es-module imports (this is optional though). So instead of importing from
import './node_modules/leaflet/dist/leaflet';
instead try:
import {map as createMap, tileLayer} from './node_modules/leaflet/dist/leaflet-src.esm.js';
This is because it usually fits in better with the modular nature of webcomponents as well as es modules can be optimized by bundlers much more effectively than calls to window.L
Next, rewrite your leaflet calls for this syntax. e.g.
let map = L.map('mapid').setView([51.505, -0.09], 13);
// should be
let map = createMap('mapid').setView([51.505, -0.09], 13);
// and
map.addLayer(L.tileLayer(urlTemplate, {minZoom: 4}))
// should be
map.addLayer(tileLayer(urlTemplate, {minZoom: 4}))
Next, libraries that require a global ID to function tend to have problems in web components that use shadow DOM as Shadow Roots scope DOM and thus queries to their roots. Though, luckily, leaflet's documentation shows that we can instead pass it an element reference rather than just an id. This means we need to get the element reference like so:
const mapEl = this.shadowRoot.querySelector('#mapid');
which we can then pass into the createMap
function
const mapEl = this.shadowRoot.querySelector('#mapid');
let map = createMap(mapEl).setView([51.505, -0.09], 13);
And finally we'll see that we run into the issue where the mapEl
is null
. The reason this is is because LitElement won't render it's contents until the firstUpdated
lifecycle callback. This means we have to change the location of map creation from connectedCallback
to firstUpdated
.
Here is a working example of your code in which I've also added a little bit of styling to give the custom element some height: https://jsbin.com/fumusodake/edit?html,output