7

im trying to creater a marker with popup on click, so far so good, the problem is when im trying to set the content of the popup to be my custom tag, for example

let popup = new mapboxgl.Popup()
    .setHTML("<custom-tag></custom-tag>") 

I know about the option of setDOMContent but I didn't manage to get it right... it suppose to work with document.createElement('custom-tag') so if you can help me on how to use it with custom components. thank you for your help!

1 Answers1

18

I was able to get this to work using the Angular ComponentFactoryResolver. There's a bit of setup, but once you get it working, you can use it to render any component you want (and put it anyplace you'd like...including a mapbox popup).

I'm not sure if this is still the "right" way to do this (I'm still on Angular v5) but it does work.

1) Create Dynamic Component Service (can't recall where I got this...sorry for no attribution whoever you are)

import { Injectable, Injector, ApplicationRef, ComponentFactoryResolver, ComponentRef, Type } from '@angular/core'

@Injectable()
export class DynamicComponentService {

    private compRef: ComponentRef<any>;

    constructor(private injector: Injector,
                private resolver: ComponentFactoryResolver,
                private appRef: ApplicationRef) { }


    public injectComponent<T>(component: Type<T>, propertySetter?: (type: T) => void): HTMLDivElement {
        // Remove the Component if it Already Exists
        if (this.compRef) this.compRef.destroy();

        // Resolve the Component and Create
        const compFactory = this.resolver.resolveComponentFactory(component);
        this.compRef = compFactory.create(this.injector);

        // Allow a Property Setter to be Passed in (To Set a Model Property, etc)
        if (propertySetter)
            propertySetter(this.compRef.instance);

        // Attach to Application
        this.appRef.attachView(this.compRef.hostView);

        // Create Wrapper Div and Inject Html
        let div = document.createElement('div');
        div.appendChild(this.compRef.location.nativeElement);

        // Return the Rendered DOM Element
        return div;
    }
}

2) Use the service to render your custom component in the mapbox-gl popup

import { MyCustomMapboxPopup } from "../app/components/my-custom-mapbox-popup.component"
import { DynamicComponentService } from "../services/dynamic-component";

...
// Inside a map.on("click") or wherever you want to create your popup

// Inject Component and Render Down to HTMLDivElement Object
let popupContent = this.dynamicComponentService.injectComponent(
                MyCustomMapboxPopup,
                x => x.model = new PopupModel()); // This Is where You can pass
// a Model or other Properties to your Component

 new mapboxgl.Popup({ closeOnClick: false })
     .setLngLat(...wherever you want the popup to show) 
     .setDOMContent(popupContent)
     .addTo(map);
...

Just to avoid any confusion, the custom popup component might look something like:

import { Component } from '@angular/core';

@Component({
    selector: "custom-mapbox-popup",
    templateUrl: "./my-custom-mapbox-popup.component.html"
})
export class MyCustomMapboxPopup {
    public model: PopupModel; // Model Property
}

// HTML
<div class="my-custom-popup">
    <div *ngIf="model">
        <h3>{{this.model.SomeModelProperty}}</h3>
    </div>
</div>
mikeo
  • 1,045
  • 13
  • 17
  • thank you very much , it's a good solution, but i have one question ? how i can pass data to th echild Component ? – Osama khodroj Jan 28 '21 at 12:40
  • 1
    @Osamakhodrog, it's commented where you can pass data: `x => x.model = new PopupModel()); // This Is where You can pass // a Model or other Properties to your Component`. – ANeves Feb 12 '21 at 15:56
  • ComponentFactoryResolver has been deprecated. Any idea how to replace it? – albanx Apr 02 '22 at 12:06
  • @albanx https://stackoverflow.com/a/70947152/3892531 ? I haven't converted to 13 yet, but if/when I do I'll update the answer with any details. – mikeo Apr 04 '22 at 19:03