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不处理innerHTMLappendChild ”。 这是完全死胡同吗?

第二:是否可以使用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)); });