我使用谷歌地图javascript api,我必须在InfoWindow中显示一个Angular组件。
在我的项目中,我使用Jsonp
服务加载了Google地图API。比我有google.maps.Map
对象可用。稍后在一个组件中,我创建了一些标记,并在其上附加了一个点击监听器:
TypeScript :
let marker = new google.maps.Marker(opts);
marker.setValues({placeId: item[0]});
marker.addListener('click', (ev: google.maps.MouseEvent) => this.onMarkerClick(marker, ev));
然后在click
处理程序中,我想打开一个包含Angular组件的信息窗口:
TypeScript :
private onMarkerClick(marker: google.maps.Marker, ev: google.maps.MouseEvent) {
var div = document.createElement();
this.placeInfoWindow.setContent(div);
// Magic should happen here somehow
// this.placeInfoWindow.setContent('<app-info-view-element></app-info-view-element>');
this.placeInfoWindow.open(this.map, marker);
}
我最终做的是一些香草JS:
TypeScript :
private onMarkerClick(marker: google.maps.Marker, ev: google.maps.MouseEvent) {
let div = document.createElement('div');
div.className = 'map-info-window-container';
div.style.height = '140px';
div.style.width = '240px';
this.placeInfoWindow.setContent(div);
this.placeInfoWindow.open(this.map, marker);
this.placesService.getPlace(marker.get('id')).subscribe(res => {
this.decorateInfoWindow(div, res.name, marker);
}, error => {
this.decorateInfoWindow(div, ':( Failed to load details: ', marker);
});
}
private decorateInfoWindow(containerEl: HTMLElement, title?:string, marker?:google.maps.Marker) {
let h3 = document.createElement('h3');
h3.innerText = title;
containerEl.appendChild(h3);
let buttonBar = document.createElement('div');
let editButton = document.createElement('button')
editButton.innerText = "Edit";
editButton.addEventListener('click', ev => {
this.editPlace(marker);
});
buttonBar.appendChild(editButton);
containerEl.appendChild(buttonBar);
}
问题,正如我所知,创建动态组件的唯一可行方法是使用Angulars ViewContainerRef
:
但是没有文档或示例,描述了如何从动态创建的元素创建ViewContainerRef
。
强制框架以某种方式处理DOM是否可行?正如在很多线程中所述:“Angular不处理innerHTML
或appendChild
”。这完全是死路一条吗?
第二:是否可以使用Renderer
实施? (不熟悉它),我看到了这个Canvas Renderer Experiment,理论上,我猜它也可以用于Google地图,因为我们可以推断地图只是一种特殊的画布。它在上一个版本中是否仍然可用或更改? DomRenderer
不在文档中,但是可以在源代码中找到它。
答案 0 :(得分:13)
这里的主要规则是动态创建组件,您需要将其出厂。
1)将动态组件添加到entryComponents
数组,除了包括declarations
:
@NgModule({
...
declarations: [
AppInfoWindowComponent,
...
],
entryComponents: [
AppInfoWindowComponent,
...
],
})
即使我们不在某些模板中直接使用我们的组件,这也是角度编译器为组件生成ngfactory的提示。
2)现在我们需要将ComponentFactoryResolver
注入到我们想要获得ngfactory的组件/服务中。您可以将ComponentFactoryResolver
视为组件工厂的存储
<强> app.component.ts 强>
import { ComponentFactoryResolver } from '@angular/core'
...
constructor(private resolver: ComponentFactoryResolver) {}
3)是时候让AppInfoWindowComponent
工厂了:
const compFactory = this.resolver.resolveComponentFactory(AppInfoWindowComponent);
this.compRef = compFactory.create(this.injector);
4)拥有工厂,我们可以随心所欲地使用它。以下是一些案例:
ViewContainerRef.createComponent(componentFactory,...)
在viewContainer旁边插入组件。
ComponentFactory.create(injector, projectableNodes?, rootSelectorOrNode?)
只需创建组件,此组件即可插入与rootSelectorOrNode
匹配的元素
请注意,我们可以在ComponentFactory.create
函数的第三个参数中提供节点或选择器。在许多情况下它可能会有所帮助。在这个例子中,我将简单地创建组件,然后插入到一些元素中。
onMarkerClick
方法可能如下所示:
onMarkerClick(marker, e) {
if(this.compRef) this.compRef.destroy();
// creation component, AppInfoWindowComponent should be declared in entryComponents
const compFactory = this.resolver.resolveComponentFactory(AppInfoWindowComponent);
this.compRef = compFactory.create(this.injector);
// example of parent-child communication
this.compRef.instance.param = "test";
const subscription = this.compRef.instance.onCounterIncremented.subscribe(x => { this.counter = x; });
let div = document.createElement('div');
div.appendChild(this.compRef.location.nativeElement);
this.placeInfoWindow.setContent(div);
this.placeInfoWindow.open(this.map, marker);
// 5) it's necessary for change detection within AppInfoWindowComponent
// tips: consider ngDoCheck for better performance
this.appRef.attachView(this.compRef.hostView);
this.compRef.onDestroy(() => {
this.appRef.detachView(this.compRef.hostView);
subscription.unsubscribe();
});
}
5)不幸的动态创建组件不是变更检测树的一部分,因此我们还需要注意变更检测。可以使用ApplicationRef.attachView(compRef.hostView)
完成,如上面的示例所示,或者我们可以使用ngDoCheck
(example)明确表达我们正在创建动态组件的组件(AppComponent
{1}}在我的情况下)
<强> app.component.ts 强>
ngDoCheck() {
if(this.compRef) {
this.compRef.changeDetectorRef.detectChanges()
}
}
这种方法更好,因为它只会在更新当前组件时更新动态组件。另一方面,ApplicationRef.attachView(compRef.hostView)
将变化检测器添加到变化检测器树的根,因此每次变化检测时都会调用它。
<强> Plunker Example 强>
提示:
因为addListener
在angular2区域之外运行,我们需要明确地在angular2区域内运行我们的代码:
marker.addListener('click', (e) => {
this.zone.run(() => this.onMarkerClick(marker, e));
});