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

Inside my angular 2 app, I have a flyer map with a popup associated with an onClick event.

The popup content has a link to the angular component. however, when I use routerLink inside the .setContent () function, the link is not displayed.

I am guessing this is because .setContent () cannot render angular 2 directives, which makes sense. what can i use instead?

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})

export class MapComponent implements AfterViewInit {

  openmap: any;

  constructor() { }

  ngAfterViewInit() {

    let openmap = L.tileLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", {
      attribution: 'terms and feedback'
    });

    let map = L.map("map", {
      center: [33.2148, -97.1331],
      zoom: 5,
      zoomControl: true,
      maxZoom: 18 
    }).addLayer(openmap);

    let marker = L.marker([39.2148, -98.1331]).addTo(map);

    let popup = L.popup();

    function onMapClick(e) {
      popup
        .setLatLng(e.latlng)
        .setContent("Facility" + "<br/>" + "<a routerLink='/view2'>" + "View Two" + "</a>")
        .openOn(map);
    }

    map.on('click', onMapClick);
  }

}

      

Needles if I change it to

 .setContent("Facility" + "<br/>" + "<a href='../view2'>" + "View Two" + "</a>")

      

Will do what I want, but it will cause the page to refresh, so this is not an option.

+5


source to share


3 answers


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

A simple approach is to use raw HTML with an anchor element outside angular without RouterLink . Register to click this binding element and use the Router service for navigation.

The challenge was to exchange links, but the actual problem is much deeper, now it is linking next time showing the angular component ...

So, for a comprehensive solution:

This is a very complex topic ... Not only does it involve the use of angular best practices, which it has also pushed forward in implementing the flyer.

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

First is the angular scope.

An HTML string containing directives, components or channels will never work, the only way is to initialize the View

Define A View as a reference to a component view instance, or...

They are called ComponentRef and TemplateRef

So, we have 2 ways to solve this problem. Since I cannot do this, I will go with ComponentRef , but note that you can use TemplateRef as well . With templates, you first need to get the template defined in the component as well as the ViewContainerRef to attach that template to.

We will create a service that takes a Marker leaf and binds to the click event of the marker, after which it will open a popup which is angular.

The component is simple, it displays 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 takes a token and a link and registers a click event.

When the popup method fires, it uses angular tools to instantiate the FacilityLinkComponent view , set the link to bind to the future, attach the view to it and attach that to the DOM.

All this happens in the first 5 lines of code.

Some notes:

  • We need to attach a view to change how detection works.
  • A correct implementation will allow you to set the ViewContainerRef and / or Injector - this is required when using lazy loading.
  • It is preferable to send data to the component via an injector rather than by destination (ReflectiveInjector)
  • Proper cleanup required (destroy component and detach view)
  • You need to add switching logic and also clear navigation.

leaflet

The code from line 6 does the positioning of the popup.

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

This is why I used a marker, so I will have a positioning reference.

In the real world example, you need to get the panel and point the components to their own layer by calculating the position. It's not that hard as the flyer has all the helper in it, but it was too much for that.

Hope it helps.

+8


source


I had the same requirement. I just did it the easy 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])
      })
    });
  }

      

+1


source


** This answer doesn't work. I'll leave that for now in case anyone has a solution. I think this shows the problem that Angular needs SafeHtml for routerLink (see DomSanitizer ) and Leaflet will pass the string to the popup using . setContent () or . bindPopup ()

The code below is based on Contribute to Angular2

export class MapComponent implements AfterViewInit {

  constructor(private _sanitizer: DomSanitizer){}

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

  private htmlProperty(): SafeHtml {
     return this._sanitizer.bypassSecurityTrustHtml(this._popUpContent);
  }

  ngAfterViewInit() {
    let safeLink = this.htmlProperty();

    let openmap = L.tileLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", {
      attribution: 'terms and feedback'
    });

    let map = L.map("map", {
      center: [33.2148, -97.1331],
      zoom: 5,
      zoomControl: true,
      maxZoom: 18 
    }).addLayer(openmap);

    let marker = L.marker([39.2148, -98.1331]).addTo(map);

    let popup = L.popup();

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

    map.on('click', onMapClick);
  }
}

      

0


source







All Articles