google-mapsliferay-7stenciljsliferay-theme

Implementing google markerclusterer plus into stencil webcomponents


I read a lot about how to implementing external files into stencil but nothing worked yet like I want it. The speciality is that stencil runs in a Liferay theme. But I think that shouldn't matter.

I installed the MarkerClustererPlus from google via npm and in the stencil config i copy the min file into an "asset" folder like this:

import { Config } from '@stencil/core';
import { sass } from '@stencil/sass';
import nodePolyfills from 'rollup-plugin-node-polyfills';

export const config: Config = {
  namespace: 'gwkp-components',
  srcDir: 'src/js/components',
  plugins: [
    sass({
      injectGlobalPaths: [
        'build/css/clay/_mixins.scss',
        'build/css/fonts/font-awesome/scss/_mixins.scss',
        'build/css/fonts/font-awesome/scss/_variables.scss',
        'build/css/fonts/font-awesome/scss/_core.scss',
        'src/js/components/utils/_stencilvars.scss',
        'build/css/fonts/font-awesome/scss/regular.scss',
        'build/css/fonts/font-awesome/scss/light.scss',
        'build/css/fonts/font-awesome/scss/solid.scss',
        'build/css/clay/bootstrap/_functions.scss',
        'build/css/clay/bootstrap/_mixins.scss',
        'build/css/clay/bootstrap/_variables.scss',
        'src/css/gw/_colors.scss',
        'src/css/gw/_variables.scss',
        'src/css/gw/bem/_includes.scss'
      ]
    })
  ],
  outputTargets: [
    {
      type: 'www',
      buildDir: '.',
      dir: 'build/js/components',
      serviceWorker: null, // disable service workers
      copy: [
        { src: '../../../build/css/fonts/font-awesome/webfonts', dest: 'webfonts' },
        { src: '../../../node_modules/@google/markerclustererplus/dist/markerclustererplus.min.js', dest: 'assets/js/markerclustererplus.min.js' }
      ]
    }
  ],
  rollupPlugins: {
    after: [
      nodePolyfills()
    ]
  }
};

Now the question arises how to get access to the min.js file? Do I need an import statement and declaring a variable or create a script tag? I tried both in some way but nothing worked so far. I need it only in one component.

At the end I wanna use the following code in the component:

var markerCluster = new MarkerClusterer(this.map, this.markerMap,
            {imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'});

Solution

  • No need to copy node_modules/@google/markerclustererplus/dist/markerclustererplus.min.js to assets/js/markerclustererplus.min.js.

    In your component's tsx file just import MarkerCluster

    import MarkerClusterer from "@google/markerclustererplus";
    

    You'll need to have the google maps javascript library already loaded. As far as I'm aware you'll have to load it via a script tag but this can be done dynamically.

    I'm using // @ts-ignore in my examples but you can install the Google Maps types if you prefer.

    The final component should look something like the following:

    import { h, Component, Host, Element, Listen } from "@stencil/core";
    import MarkerClusterer from "@google/markerclustererplus";
    
    @Component({
      tag: "my-component",
      styles: `
        :host {
          position: absolute;
          width: 100%;
          height: 100%;
        }
    
        .map {
          height: 100%;
        }
      `,
      shadow: true,
    })
    export class MyComponent {
      @Element() el: HTMLElement;
    
      private googleMapsApiKey = "YOUR_API_KEY_HERE";
      private locations = [
        { lat: -31.56391, lng: 147.154312 },
        { lat: -33.718234, lng: 150.363181 },
        { lat: -33.727111, lng: 150.371124 },
        { lat: -33.848588, lng: 151.209834 },
        { lat: -33.851702, lng: 151.216968 },
        { lat: -34.671264, lng: 150.863657 },
        { lat: -35.304724, lng: 148.662905 },
        { lat: -36.817685, lng: 175.699196 },
        { lat: -36.828611, lng: 175.790222 },
        { lat: -37.75, lng: 145.116667 },
        { lat: -37.759859, lng: 145.128708 },
        { lat: -37.765015, lng: 145.133858 },
        { lat: -37.770104, lng: 145.143299 },
        { lat: -37.7737, lng: 145.145187 },
        { lat: -37.774785, lng: 145.137978 },
        { lat: -37.819616, lng: 144.968119 },
        { lat: -38.330766, lng: 144.695692 },
        { lat: -39.927193, lng: 175.053218 },
        { lat: -41.330162, lng: 174.865694 },
        { lat: -42.734358, lng: 147.439506 },
        { lat: -42.734358, lng: 147.501315 },
        { lat: -42.735258, lng: 147.438 },
        { lat: -43.999792, lng: 170.463352 },
      ];
      private _mapEl: HTMLElement;
    
      componentDidLoad() {
        if (document.getElementById("maps-script")) {
          this.initMap();
          return;
        }
    
        // Create the script tag, set the appropriate attributes
        var script = document.createElement("script");
        script.id = "maps-script";
        script.src = `https://maps.googleapis.com/maps/api/js?key=${this.googleMapsApiKey}&callback=initMap`;
        script.defer = true;
    
        // Attach your callback function to the `window` object
        // @ts-ignore
        window.initMap = () => this.initMap();
    
        // Append the 'script' element to 'head'
        document.head.appendChild(script);
      }
    
      initMap() {
        // @ts-ignore
        const map = new google.maps.Map(this._mapEl, {
          zoom: 3,
          center: { lat: -28.024, lng: 140.887 },
        });
    
        // Create an array of alphabetical characters used to label the markers.
        const labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        // Add some markers to the map.
        // Note: The code uses the JavaScript Array.prototype.map() method to
        // create an array of markers based on a given "locations" array.
        // The map() method here has nothing to do with the Google Maps API.
        const markers = this.locations.map((location, i) => {
          // @ts-ignore
          return new google.maps.Marker({
            position: location,
            label: labels[i % labels.length],
          });
        });
    
        // Add a marker clusterer to manage the markers.
        const markerCluster = new MarkerClusterer(map, markers, {
          imagePath:
            "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m",
        });
      }
    
      render() {
        return (
          <Host>
            <div class="map" ref={(el) => (this._mapEl = el)}></div>
          </Host>
        );
      }
    }
    

    Here is a Working example on webcomponents.dev