Angular 2 / Leaflet地图,如何从标记弹出窗口链接到组件?... routerLink?

7

在我的angular 2应用程序中,我有一个与onClick事件绑定的leaflet地图和弹出窗口。

弹出窗口的内容包含指向angular组件的链接。然而,当我在.setContent()函数中使用routerLink时,链接不会显示。

我猜这是因为.setContent()无法呈现Angular 2指令,这很合理。我应该使用什么替代方法?

@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);
  }

}

毋庸置疑,如果我将其改为

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

这样做可以实现我想要的功能,但会导致页面刷新,因此不是一个好选择。


有没有人有解决方案? - somecallmemike
似乎不是这样。我的地图链接还很慢,我仍需要它 :( - brohymn
4个回答

10

有一个非常简单的方法和一个非常复杂的方法。

简单的方法是在Angular之外使用带有锚元素的原始HTML而不使用RouterLink。注册该锚元素上的点击事件,然后使用Router服务导航。

任务是触发链接,但实际问题远比这更深入,现在它连接下一次显示的是一个Angular组件...

因此,对于复杂的解决方案:

这是一个非常高级的主题...不仅涉及使用高级的Angular技术,也涉及到leaflet的高级实现。

我会尽力传达信息,但由于复杂性,示例将非常简单并需要进行修改。

首先- Angular领域。

包含指令、组件或管道的HTML字符串永远不会起作用,唯一的方法是初始化一个View

让我们将View定义为对组件模板的视图实例的引用。

这些称为ComponentRefTemplateRef

因此,我们有两种方法来解决这个问题。由于我不能同时执行两个操作,所以我将使用ComponentRef,但请注意,您也可以使用TemplateRef。对于模板,您首先需要获取组件中定义的模板,以及一个ViewContainerRef将该模板附加到其中。

我们将构建一个服务,该服务接受一个leaflet标记并绑定到标记的单击事件,在单击时打开一个弹出窗口,该窗口是一个Angular组件。

该组件很简单,它呈现一个链接。

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

现在,关于服务:

@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);
  }
}
register方法接受一个标记和链接,并注册到点击事件。当popup方法触发时,它使用Angular工具创建一个FacilityLinkComponent的视图实例,并设置将来进行绑定的链接,将视图附加到它并将其附加到DOM中。这一切都发生在前5行代码中。需要注意的是:我们必须附加一个视图以便变更检测正常工作;一个适当的实现允许设置ViewContainerRef和/或Injector —— 这在使用懒加载时是必需的;最好通过Injector而不是赋值(ReflectiveInjector)向组件发送数据;需要进行适当的清理(销毁组件并分离视图);需要添加切换逻辑,也要在导航时进行清理。第6行代码开始的代码执行弹出窗口的定位。这是非常简单的逻辑,它只是从标记复制所有内容。这就是我使用标记的原因,所以我有一个引用来取得位置。在实际应用中,您需要获取面板并将组件推送到它们自己的层中,计算位置。由于Leaflet拥有所有帮助程序,因此这并不困难,但对于本例来说太过繁琐。希望这有所帮助。

非常感谢,这对我帮助很大,设置有点复杂,但一旦完成,您可以在任何需要在leaflet弹出窗口上进行复杂交互的未来项目中重复使用它。大拇指向上!2条评论:
  1. 您不需要分离视图,组件销毁时会自动分离。
2:您必须拦截标准地图单击事件,如果您单击视图,leaflet会触发map.on('click'...)。我用一个hack实现了这个(如果点击了视图=>不执行通常的mapclick代码)。
- Jonas Ostergaard
1
@JonasOstergaard,你能否发布你的解决方案,最好是通过一个可工作的Github/Plunker示例? - Mohammad Ali

3

我有同样的需求,我只是用了一种简单的方法。

根据你的代码,你可以像这样添加更改

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

我的答案是基于 @Vijay Kumar 的答案。我有多个标记,希望根据标记来设置按钮的不同行为。

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') },
            }));
        });
    }
}

bindPopup 中的按钮如下:

<a imei="${device.key}" class="device-control-btn"> Device Operation</a>
<a imei="${device.key}" class="device-alarms-btn"> Device Alarms</a>

-1

这个答案不可行。我现在会保留它,以防有人有解决方案。我认为它显示了Angular需要SafeHtml来处理routerLink的问题(请参见DomSanitizer),而Leaflet只能使用.setContent().bindPopup()将字符串传递给弹出窗口。

下面的代码基于在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);
  }
}

1
我正在使用bindPopup在标记上设置弹出窗口,这需要内容为字符串,并且在SafeHtml类型上出现错误。我的代码如下:let marker = L.marker([location.latitude, location.longitude], {icon: blueIcon}).bindPopup( html ).addTo(this.map); - somecallmemike
你说得对。我的答案不起作用。就像你所描述的那样——.setContent()会接受一个函数,但只有在它返回一个字符串时才有效。htmlProperty()返回类型为SafeHtml。 - jm22
抱歉,我的评论不知何故被截断了。我在问是否有一个好的解决方案,可以允许动态注入包含routerLinks的HTML的bindPopup? - somecallmemike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接