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

前端 未结 4 1887
暗喜
暗喜 2020-12-14 12:53

Inside my angular 2 app I have a leaflet map with a popup bound to a onClick event.

The content of the popup has a link to an angular component. however when I use r

相关标签:
4条回答
  • 2020-12-14 13:28

    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.

    0 讨论(0)
  • 2020-12-14 13:34

    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])
          })
        });
      }
    
    0 讨论(0)
  • 2020-12-14 13:37

    My answer is based on the answer of @Vijay Kumar. I have multiple markers, and wanted the buttons' actions to be different based on the marker.

    map.on('popupopen', (e) => {
        const _latlng = e.target._popup._latlng;
        const lat = _latlng.lat + 4;
        map.setView([lat, _latlng.lng], e.target._zoom);
        // Events listeners
        addDeviceControlEventListener(this.elementRef, this.ngZone, this.router);
        addDeviceAlarmsEventListener(this.elementRef, this.ngZone, this.dialogService);
    });
    
    addDeviceControlEventListener(elementRef: ElementRef, ngZone: NgZone, router: Router) {
        const list = elementRef.nativeElement.querySelectorAll('a.device-control-btn');
        for (const item of list) {
            item.addEventListener('click', (e: any) => {
                ngZone.run(() => router.navigateByUrl(`pages/device/${item.getAttribute('imei')}`));
            });
        }
    }
    
    addDeviceAlarmsEventListener(elementRef: ElementRef, ngZone: NgZone, dialogService: NbDialogService) {
        const list = elementRef.nativeElement.querySelectorAll('a.device-alarms-btn');
        for (const item of list) {
            item.addEventListener('click', (e: any) => {
                ngZone.run(() => dialogService.open(DeviceAlarmsComponent, {
                    context: { deviceKey: item.getAttribute('imei') },
                }));
            });
        }
    }
    

    The buttons in bindPopup are as follows

    <a imei="${device.key}" class="device-control-btn"> Device Operation</a>
    <a imei="${device.key}" class="device-alarms-btn"> Device Alarms</a>
    
    0 讨论(0)
  • 2020-12-14 13:41

    **This answer does not work. I will leave it up for now in case someone has a solution. I think it shows the issue that Angular needs SafeHtml for a routerLink (see DomSanitizer ) and Leaflet will only pass a string to a popup using .setContent() or .bindPopup()

    The code below is based on Sanitize input in 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 讨论(0)
提交回复
热议问题