Angular2 – 将组件分解为dynamic创build的元素
我使用谷歌地图的JavaScript API,我必须在InfoWindow中显示一个Angular组件。
在我的项目中,我使用Jsonp
服务加载google map api。 比我有google.maps.Map
对象可用。 稍后在一个组件中,我创build了一些标记并附加到一个点击监听器:
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); }
据我所知,问题是创builddynamic组件的唯一可行方法是使用Angulars ViewContainerRef
:
- 如何在容器中放置一个dynamic组件
但没有文档或示例,描述如何从dynamic创build的元素创buildViewContainerRef
。
强制框架以某种方式处理DOM是否可行? 正如它在很multithreading中所述:“Angular不处理innerHTML
或appendChild
”。 这是完全死胡同吗?
第二:是否可以使用Renderer
实现? (不熟悉它),我看到了这个Canvas Renderer实验 ,理论上,我猜这也适用于Google地图,因为我们可以推断出地图只是一种特殊的canvas。 它在上一个版本中仍然可用,或者它已更改? DomRenderer
不在文档中,但是可以在源文件中find它。
这里的主要规则是dynamic创build组件,您需要获得它的工厂。
1)除了包含declarations
之外,还向entryComponents
数组添加dynamic组件:
@NgModule({ ... declarations: [ AppInfoWindowComponent, ... ], entryComponents: [ AppInfoWindowComponent, ... ], })
这是angular度编译器为组件生成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?)
只是创build组件,并且可以将此组件插入到匹配rootSelectorOrNode
请注意,我们可以在ComponentFactory.create
函数的第三个参数中提供节点或select器。 在许多情况下,这可能会有所帮助。 在这个例子中,我将简单地创build组件,然后插入一些元素。
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)不幸dynamic创build的组件不是变化检测树的一部分,因此我们还需要关心变化检测。 可以通过使用ApplicationRef.attachView(compRef.hostView)
来完成,或者我们可以用ngDoCheck
( 示例 )来创builddynamic组件(我的例子中为AppComponent
)
app.component.ts
ngDoCheck() { if(this.compRef) { this.compRef.changeDetectorRef.detectChanges() } }
这种方法更好,因为如果更新当前组件,它只会更新dynamic组件。 另一方面, ApplicationRef.attachView(compRef.hostView)
将变化检测器添加到变化检测器树的根,因此它将在每个变化检测时刻被调用。
Plunker例子
提示:
由于addListener
在angular2区域之外运行,我们需要在angular2区域内运行我们的代码:
marker.addListener('click', (e) => { this.zone.run(() => this.onMarkerClick(marker, e)); });