Angular2 - 组成动态创建的元素

Tib*_* C. 10 google-maps angular

我使用谷歌地图javascript api,我必须在InfoWindow中显示一个Angular组件.

在我的项目中,我使用该Jsonp服务加载谷歌地图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));
Run Code Online (Sandbox Code Playgroud)

然后在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);
}
Run Code Online (Sandbox Code Playgroud)

我最终做的是一些香草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);
  }
Run Code Online (Sandbox Code Playgroud)

正如我所知,问题是创建动态组件的唯一可行方法是使用Angulars ViewContainerRef:

但是没有文档或示例,描述了如何ViewContainerRef从动态创建的元素创建.


是否可以强制框架以某种方式处理DOM?正如在很多线程中所述:"Angular不处理innerHTMLappendChild".这完全是死路一条吗?

第二:是否可以使用Renderer实施?(不熟悉它),我看到了这个Canvas渲染器实验,理论上,我猜它也适用于谷歌地图,因为我们可以推断地图只是一种特殊的画布.它在上一个版本中是否仍然可用或更改?DomRenderer不在文档中,但是可以在源代码中找到它.

yur*_*zui 14

这里的主要规则是动态创建组件,您需要获得它的工厂.

1)将动态组件添加到entryComponents数组中除了包括declarations:

@NgModule({
  ...
  declarations: [ 
    AppInfoWindowComponent,
    ...
  ],
  entryComponents: [
    AppInfoWindowComponent,
    ...
  ],
})
Run Code Online (Sandbox Code Playgroud)

即使我们不在某些模板中直接使用我们的组件,这也是角度编译器为组件生成ngfactory的提示.

2)现在我们需要注入ComponentFactoryResolver我们想要获得ngfactory的组件/服务.您可以将ComponentFactoryResolver其视为组件工厂的存储

app.component.ts

import { ComponentFactoryResolver } from '@angular/core'
...
constructor(private resolver: ComponentFactoryResolver) {}
Run Code Online (Sandbox Code Playgroud)

3)是时候AppInfoWindowComponent出厂了:

const compFactory = this.resolver.resolveComponentFactory(AppInfoWindowComponent);
this.compRef = compFactory.create(this.injector);
Run Code Online (Sandbox Code Playgroud)

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();
  });
}
Run Code Online (Sandbox Code Playgroud)

5)不幸的动态创建组件不是变更检测树的一部分,因此我们还需要注意变更检测.它可以通过使用ApplicationRef.attachView(compRef.hostView)上面的例子中所写的来完成,或者我们可以用我们创建动态组件的组件ngDoCheck(示例)明确表达(AppComponent在我的例子中)

app.component.ts

ngDoCheck() {
  if(this.compRef) {
    this.compRef.changeDetectorRef.detectChanges()
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法更好,因为它只会在更新当前组件时更新动态组件.另一方面,ApplicationRef.attachView(compRef.hostView)将变化检测器添加到变化检测器树的根,因此将在每个变化检测标记上调用它.

Plunker示例


提示:

因为addListener在angular2区域外运行,我们需要明确地在angular2区域内运行我们的代码:

marker.addListener('click', (e) => { 
  this.zone.run(() => this.onMarkerClick(marker, e));
});
Run Code Online (Sandbox Code Playgroud)