Angular 2 / leaflet map, How to link to a component from marker popup ? ... routerLink?

There is a very simple approach and a very complex one.

The simple approach is to use raw HTML with anchor element outside of angular without RouterLink. Register to clicks on that anchor element and use the Router service to navigate.

The task was to fire links but the actual problem is far deeper, now it links next time its showing an angular component...

So, for the complex solution:

This is an highly advanced topic... Not only it involves using advanced angular techniques it's also advanced in the leaflet implementation.

I'll do my best to convey the message but due to the complexity the examples will be very simple and will require work.

First - Angular realm.

An HTML string that contains directives, components or pipes will never work, the only way is to initialize a View

Let's define A View as a reference to view instance of a component or a template.

These are called ComponentRef and TemplateRef

So, we have 2 ways to solve this problem. Since I can't do both i'll go with ComponentRef but note that you can also use TemplateRef. With templates you'll first need to obtain a template defined in the component as well as a ViewContainerRef to attach that template to.

We will build a service that accepts a leaflet Marker and binds to the click event of the marker, on click it will open a popup which is an angular Component.

The component is simple, it renders a link.

@Component({
  selector: 'facility-link',
  template: `Facility <br/> <a routerLink="{{link}}"> View Two</a>`
})
export class FacilityLinkComponent {
  public link: string;
  constructor() { }
}

Now, for the service:

@Injectable()
export class LinkPopupService {

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


  register(marker: leaflet.Marker, link: string): void  {
    marker.on('click', ($event: leaflet.MouseEvent)  => this.popup($event.target, link) );
  }

  popup(marker: leaflet.Marker, link: string) {
    const cmpFactory = this.cfr.resolveComponentFactory(FacilityLinkComponent);
    const componentRef = cmpFactory.create(this.injector);
    componentRef.instance.link = link;
    this.appRef.attachView(componentRef.hostView);
    const markerElement = marker.getElement();
    markerElement.parentElement.appendChild(componentRef.location.nativeElement);

    const markerPos = leaflet.DomUtil.getPosition(markerElement);
    const markerClass = leaflet.DomUtil.getClass(markerElement);


    leaflet.DomUtil.setTransform(componentRef.location.nativeElement, markerPos);
    leaflet.DomUtil.setClass(componentRef.location.nativeElement, markerClass);
  }
}

The register method accepts a marker and the link and registers to the click event.

When the popup method fires it uses angular tools to create a view instance of FacilityLinkComponent, set the link for future binding, attach a view to it and attach it to the DOM.

This all happens in the first 5 lines of code.

Some notes:

  • We must attach a view so change detection works
  • A Proper implementation will allow to set ViewContainerRef and / or an Injector - this is a must when using lazy loading.
  • It is preferred sending data to the component via Injector and not by assignment (ReflectiveInjector)
  • Proper clean up is required (destroy the component and detach the view)
  • Need to add toggle logic, also clean on navigation.

Leaflet

The code from the 6th line performs positioning of the popup.

This is a very simple logic, it just copies everything from the marker.

This is why I used a marker, so I'll have a reference to take the positioning from.

In a realworld example you'll need to get a panel and push the components into their own layer, computing the position. This is not that difficult since leaflet has all the helper, but it was too much for this.

Hope it helps.


I had the same requirement. I just did it in a simple way.

As per your code you can add changes like this

private _popUpContent: string = '"Facility" + "<br/>" + "<a id="view_two">" + "View Two" + "</a>"';

constructor(private _sanitizer: DomSanitizer , public ngZone : NgZone,public elementRef : ElementRef){}

 function onMapClick(e) {
      popup
        .setLatLng(e.latlng)
        .setContent(safeLink) //throws type error
        .openOn(map);

        popup.on('popupopen' , () => {
          this.initInfoWindowEventListner();
        })
    }

function initInfoWindowEventListner(){
    this.elementRef.nativeElement.querySelector("#view_two")
    .addEventListener('click', (e : any)=> {
      this.ngZone.run(() => {
        this.router.navigate(['/view2])
      })
    });
  }