如何在 Leaflet 标记的弹出窗口中生成 Angular 4 组件?
How to spawn Angular 4 component inside a Leaflet marker's popup?
我已经是 Angular 1.x 的老用户了,现在我正在使用 Angular 4 制作一个新的应用程序。我仍然没有掌握大部分内容概念,但我终于有了一些非常好的东西。但是,我遇到了一个问题,我需要使用 Leaflet 在标记的弹出窗口中显示 Angular 4 组件(尽管在 1.x 中我只是使用了指令)。
现在,在 Angular 1.x 中,我可以针对其中包含指令 (`<component>{{ text }}</component>`
) 和按钮等的模板使用 $compile 并且它可以工作,但是 Angular 4 与它的 AoT 完全不同,在运行时编译似乎真的很难,没有简单的解决方案。
我问了一个问题 here,作者说我可以使用指令。我不确定这是否是正确的方法,甚至不确定如何将我自己的代码与他提出的解决方案混合......所以我用 Angular 4 和 Leaflet 已经建立了一个基于 npm 的小项目,以防你知道如何帮助我或想尝试一下(非常感谢!)。我已经为此苦思冥想了大约一个星期,我真的厌倦了尝试很多没有成功的替代方案:(
这是我在 GitHub 中的回购 link:https://github.com/darkguy2008/leaflet-angular4-issue
想法是在标记内生成 PopupComponent(或类似的任何东西),您可以在 src/app/services/map.service.ts 的第 38 行中找到代码。
提前致谢! :)
编辑
我设法解决了 :) 有关详细信息,请参阅标记的答案,或查看此差异。有一些注意事项,Angular 4 和 Leaflet 的程序有点不同,不需要太多更改:https://github.com/darkguy2008/leaflet-angular4-issue/commit/b5e3881ffc9889645f2ae7e65f4eed4d4db6779b
我还根据 here 解释的解决方案制作了自定义编译服务,并上传到相同的 GitHub 存储库。谢谢@yurzui! :)
好吧,感谢@ghybs 的建议,我再次尝试 link 并设法解决了问题 :D。 Leaflet 与 Google Maps 有点不同(它也更短),那里提出的解决方案可能更小更容易理解,所以这是我使用 Leaflet 的版本。
基本上,您需要将弹出组件放在主应用模块的 entryComponents
字段中。关键是在 m.onclick()
中,在那里,我们创建一个组件,在 div
中呈现它,然后我们将 div
的内容传递给传单弹出容器元素。有点棘手,但它有效。
我抽空将此解决方案转换为新的 $compile Angular 4. 检查详细信息 here。谢谢@yurzui! :)
这是核心代码...其他内容(css、webpack 等)与 OP 在同一个 repo 中,简化为几个文件:https://github.com/darkguy2008/leaflet-angular4-issue 但你只是需要这个例子来让它工作:
import 'leaflet';
import './main.scss';
import "reflect-metadata";
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import { BrowserModule } from "@angular/platform-browser";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { Component, NgModule, ComponentRef, Injector, ApplicationRef, ComponentFactoryResolver, Injectable, NgZone } from "@angular/core";
// ###########################################
// App component
// ###########################################
@Component({
selector: "app",
template: `<section class="app"><map></map></section>`
})
class AppComponent { }
// ###########################################
// Popup component
// ###########################################
@Component({
selector: "popup",
template: `<section class="popup">Popup Component! :D {{ param }}</section>`
})
class PopupComponent { }
// ###########################################
// Leaflet map service
// ###########################################
@Injectable()
class MapService {
map: any;
baseMaps: any;
markersLayer: any;
public injector: Injector;
public appRef: ApplicationRef;
public resolver: ComponentFactoryResolver;
public compRef: any;
public component: any;
counter: number;
init(selector) {
this.baseMaps = {
CartoDB: L.tileLayer("http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png", {
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="http://cartodb.com/attributions">CartoDB</a>'
})
};
L.Icon.Default.imagePath = '.';
L.Icon.Default.mergeOptions({
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
this.map = L.map(selector);
this.baseMaps.CartoDB.addTo(this.map);
this.map.setView([51.505, -0.09], 13);
this.markersLayer = new L.FeatureGroup(null);
this.markersLayer.clearLayers();
this.markersLayer.addTo(this.map);
}
addMarker() {
var m = L.marker([51.510, -0.09]);
m.bindTooltip('Angular 4 marker (PopupComponent)');
m.bindPopup(null);
m.on('click', (e) => {
if (this.compRef) this.compRef.destroy();
const compFactory = this.resolver.resolveComponentFactory(this.component);
this.compRef = compFactory.create(this.injector);
this.compRef.instance.param = 0;
setInterval(() => this.compRef.instance.param++, 1000);
this.appRef.attachView(this.compRef.hostView);
this.compRef.onDestroy(() => {
this.appRef.detachView(this.compRef.hostView);
});
let div = document.createElement('div');
div.appendChild(this.compRef.location.nativeElement);
m.setPopupContent(div);
});
this.markersLayer.addLayer(m);
return m;
}
}
// ###########################################
// Map component. These imports must be made
// here, they can't be in a service as they
// seem to depend on being loaded inside a
// component.
// ###########################################
@Component({
selector: "map",
template: `<section class="map"><div id="map"></div></section>`,
})
class MapComponent {
marker: any;
compRef: ComponentRef<PopupComponent>;
constructor(
private mapService: MapService,
private injector: Injector,
private appRef: ApplicationRef,
private resolver: ComponentFactoryResolver
) { }
ngOnInit() {
this.mapService.init('map');
this.mapService.component = PopupComponent;
this.mapService.appRef = this.appRef;
this.mapService.compRef = this.compRef;
this.mapService.injector = this.injector;
this.mapService.resolver = this.resolver;
this.marker = this.mapService.addMarker();
}
}
// ###########################################
// Main module
// ###########################################
@NgModule({
imports: [
BrowserModule
],
providers: [
MapService
],
declarations: [
AppComponent,
MapComponent,
PopupComponent
],
entryComponents: [
PopupComponent
],
bootstrap: [AppComponent]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
- 您需要为弹出内容创建一个组件,以防您还没有。让我们假设它被称为
MycustomPopupComponent
.
- 将您的组件添加到条目组件数组中的
app.module.ts
。动态创建组件时需要这个:
entryComponents: [
...,
MycustomPopupComponent
],
- 在您的屏幕中,将这两个依赖项添加到构造函数中:
constructor(
...
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector
) {
- 现在,在该屏幕中,我们可以定义一个动态创建组件的函数。
private createCustomPopup() {
const factory = this.componentFactoryResolver.resolveComponentFactory(MycustomPopupComponent);
const component = factory.create(this.injector);
//Set the component inputs manually
component.instance.someinput1 = "example";
component.instance.someinput2 = "example";
//Subscribe to the components outputs manually (if any)
component.instance.someoutput.subscribe(() => console.log("output handler fired"));
//Manually invoke change detection, automatic wont work, but this is Ok if the component doesn't change
component.changeDetectorRef.detectChanges();
return component.location.nativeElement;
}
- 最后,在创建 Leaflet 弹出窗口时,将该函数作为参数传递给 bindPopup。该函数还接受带有选项的第二个参数。:
const marker = L.marker([latitude, longitude]).addTo(this.map);
marker.bindPopup(() => this.createCustomPopup()).openPopup();
我已经是 Angular 1.x 的老用户了,现在我正在使用 Angular 4 制作一个新的应用程序。我仍然没有掌握大部分内容概念,但我终于有了一些非常好的东西。但是,我遇到了一个问题,我需要使用 Leaflet 在标记的弹出窗口中显示 Angular 4 组件(尽管在 1.x 中我只是使用了指令)。
现在,在 Angular 1.x 中,我可以针对其中包含指令 (`<component>{{ text }}</component>`
) 和按钮等的模板使用 $compile 并且它可以工作,但是 Angular 4 与它的 AoT 完全不同,在运行时编译似乎真的很难,没有简单的解决方案。
我问了一个问题 here,作者说我可以使用指令。我不确定这是否是正确的方法,甚至不确定如何将我自己的代码与他提出的解决方案混合......所以我用 Angular 4 和 Leaflet 已经建立了一个基于 npm 的小项目,以防你知道如何帮助我或想尝试一下(非常感谢!)。我已经为此苦思冥想了大约一个星期,我真的厌倦了尝试很多没有成功的替代方案:(
这是我在 GitHub 中的回购 link:https://github.com/darkguy2008/leaflet-angular4-issue
想法是在标记内生成 PopupComponent(或类似的任何东西),您可以在 src/app/services/map.service.ts 的第 38 行中找到代码。
提前致谢! :)
编辑
我设法解决了 :) 有关详细信息,请参阅标记的答案,或查看此差异。有一些注意事项,Angular 4 和 Leaflet 的程序有点不同,不需要太多更改:https://github.com/darkguy2008/leaflet-angular4-issue/commit/b5e3881ffc9889645f2ae7e65f4eed4d4db6779b
我还根据 here 解释的解决方案制作了自定义编译服务,并上传到相同的 GitHub 存储库。谢谢@yurzui! :)
好吧,感谢@ghybs 的建议,我再次尝试 link 并设法解决了问题 :D。 Leaflet 与 Google Maps 有点不同(它也更短),那里提出的解决方案可能更小更容易理解,所以这是我使用 Leaflet 的版本。
基本上,您需要将弹出组件放在主应用模块的 entryComponents
字段中。关键是在 m.onclick()
中,在那里,我们创建一个组件,在 div
中呈现它,然后我们将 div
的内容传递给传单弹出容器元素。有点棘手,但它有效。
我抽空将此解决方案转换为新的 $compile Angular 4. 检查详细信息 here。谢谢@yurzui! :)
这是核心代码...其他内容(css、webpack 等)与 OP 在同一个 repo 中,简化为几个文件:https://github.com/darkguy2008/leaflet-angular4-issue 但你只是需要这个例子来让它工作:
import 'leaflet';
import './main.scss';
import "reflect-metadata";
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import { BrowserModule } from "@angular/platform-browser";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { Component, NgModule, ComponentRef, Injector, ApplicationRef, ComponentFactoryResolver, Injectable, NgZone } from "@angular/core";
// ###########################################
// App component
// ###########################################
@Component({
selector: "app",
template: `<section class="app"><map></map></section>`
})
class AppComponent { }
// ###########################################
// Popup component
// ###########################################
@Component({
selector: "popup",
template: `<section class="popup">Popup Component! :D {{ param }}</section>`
})
class PopupComponent { }
// ###########################################
// Leaflet map service
// ###########################################
@Injectable()
class MapService {
map: any;
baseMaps: any;
markersLayer: any;
public injector: Injector;
public appRef: ApplicationRef;
public resolver: ComponentFactoryResolver;
public compRef: any;
public component: any;
counter: number;
init(selector) {
this.baseMaps = {
CartoDB: L.tileLayer("http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png", {
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="http://cartodb.com/attributions">CartoDB</a>'
})
};
L.Icon.Default.imagePath = '.';
L.Icon.Default.mergeOptions({
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
this.map = L.map(selector);
this.baseMaps.CartoDB.addTo(this.map);
this.map.setView([51.505, -0.09], 13);
this.markersLayer = new L.FeatureGroup(null);
this.markersLayer.clearLayers();
this.markersLayer.addTo(this.map);
}
addMarker() {
var m = L.marker([51.510, -0.09]);
m.bindTooltip('Angular 4 marker (PopupComponent)');
m.bindPopup(null);
m.on('click', (e) => {
if (this.compRef) this.compRef.destroy();
const compFactory = this.resolver.resolveComponentFactory(this.component);
this.compRef = compFactory.create(this.injector);
this.compRef.instance.param = 0;
setInterval(() => this.compRef.instance.param++, 1000);
this.appRef.attachView(this.compRef.hostView);
this.compRef.onDestroy(() => {
this.appRef.detachView(this.compRef.hostView);
});
let div = document.createElement('div');
div.appendChild(this.compRef.location.nativeElement);
m.setPopupContent(div);
});
this.markersLayer.addLayer(m);
return m;
}
}
// ###########################################
// Map component. These imports must be made
// here, they can't be in a service as they
// seem to depend on being loaded inside a
// component.
// ###########################################
@Component({
selector: "map",
template: `<section class="map"><div id="map"></div></section>`,
})
class MapComponent {
marker: any;
compRef: ComponentRef<PopupComponent>;
constructor(
private mapService: MapService,
private injector: Injector,
private appRef: ApplicationRef,
private resolver: ComponentFactoryResolver
) { }
ngOnInit() {
this.mapService.init('map');
this.mapService.component = PopupComponent;
this.mapService.appRef = this.appRef;
this.mapService.compRef = this.compRef;
this.mapService.injector = this.injector;
this.mapService.resolver = this.resolver;
this.marker = this.mapService.addMarker();
}
}
// ###########################################
// Main module
// ###########################################
@NgModule({
imports: [
BrowserModule
],
providers: [
MapService
],
declarations: [
AppComponent,
MapComponent,
PopupComponent
],
entryComponents: [
PopupComponent
],
bootstrap: [AppComponent]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
- 您需要为弹出内容创建一个组件,以防您还没有。让我们假设它被称为
MycustomPopupComponent
. - 将您的组件添加到条目组件数组中的
app.module.ts
。动态创建组件时需要这个:
entryComponents: [
...,
MycustomPopupComponent
],
- 在您的屏幕中,将这两个依赖项添加到构造函数中:
constructor(
...
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector
) {
- 现在,在该屏幕中,我们可以定义一个动态创建组件的函数。
private createCustomPopup() {
const factory = this.componentFactoryResolver.resolveComponentFactory(MycustomPopupComponent);
const component = factory.create(this.injector);
//Set the component inputs manually
component.instance.someinput1 = "example";
component.instance.someinput2 = "example";
//Subscribe to the components outputs manually (if any)
component.instance.someoutput.subscribe(() => console.log("output handler fired"));
//Manually invoke change detection, automatic wont work, but this is Ok if the component doesn't change
component.changeDetectorRef.detectChanges();
return component.location.nativeElement;
}
- 最后,在创建 Leaflet 弹出窗口时,将该函数作为参数传递给 bindPopup。该函数还接受带有选项的第二个参数。:
const marker = L.marker([latitude, longitude]).addTo(this.map);
marker.bindPopup(() => this.createCustomPopup()).openPopup();