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

时间:2017-04-17 22:22:09

标签: angular typescript leaflet angular2-routing typescript2.0

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

弹出窗口的内容包含指向角度组件的链接。但是当我在.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);
  }

}

Needles,如果我将其更改为

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

会做我想要的,但这会导致页面刷新,所以这不是一个选项。

4 个答案:

答案 0 :(得分:8)

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

简单的方法是在没有 RouterLink 的情况下使用带有angular元素的原始HTML。注册点击该锚元素并使用路由器服务进行导航。

任务是发射链接,但实际问题更深入,现在它下次链接显示角度组件......

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

这是一个非常高级的主题......不仅涉及使用先进的角度技术,它在传单实现中也是先进的。

我会尽力传达信息,但由于复杂性,示例非常简单,需要工作。

第一个 - Angular领域。

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

让我们将A 查看定义为查看组件模板的实例的参考。

这些被称为 ComponentRef TemplateRef

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

我们将构建一个接受传单标记的服务,并绑定到标记的click事件,点击它将打开一个弹出的角度组件。

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

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

注册方法接受标记和链接并注册到click事件。

弹出方法触发时,它使用角度工具创建 FacilityLinkComponent 的视图实例,设置未来绑定的链接,附加视图并将其附加到DOM。

这一切都发生在前5行代码中。

一些注意事项:

  • 我们必须附加一个视图,以便更改检测工作
  • 正确的实现将允许设置 ViewContainerRef 和/或 Injector - 这是使用延迟加载时必须的。
  • 最好通过Injector而不是通过赋值(ReflectiveInjector)将数据发送到组件
  • 需要正确清理(破坏组件并分离视图)
  • 需要添加切换逻辑,也可以在导航时清除。

单张

第6行的代码执行弹出窗口的定位。

这是一个非常简单的逻辑,它只是复制标记中的所有内容。

这就是我使用标记的原因,所以我将引用从中获取定位。

在一个真实世界的例子中,你需要获得一个面板并将组件推入他们自己的层,计算位置。这不是那么困难,因为传单有所有的帮助,但它太多了。

希望它有所帮助。

答案 1 :(得分:1)

我有相同的要求。 我只是以一种简单的方式做到了。

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

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

答案 2 :(得分:0)

**这个答案不起作用。如果有人有解决方案,我会暂时搁置它。我认为它显示了Angular需要SafeHtml用于routerLink的问题(参见DomSanitizer )和Leaflet只会使用一个字符串传递给弹出窗口 .setContent().bindPopup()

以下代码基于Sanitize input in Angular2

[["fname", "A", "lname", "B"], ["fname", "C", "lname", "D"]]

答案 3 :(得分: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>