2016-12-01 3 views
6

Я использую google maps javascript api, и я должен отображать угловой компонент в InfoWindow.Angular2 - компонент, созданный динамическим элементом

В моем проекте я загружаю карту Google api с помощью службы Jsonp. Чем у меня есть доступный объект google.maps.Map. Позже в компоненте я создаю некоторые маркера и прикрепить к ним щелчок слушатель:

машинопись:

let marker = new google.maps.Marker(opts); 
marker.setValues({placeId: item[0]}); 
marker.addListener('click', (ev: google.maps.MouseEvent) => this.onMarkerClick(marker, ev)); 

А потом в click обработчике я хочу открыть информационное окно, которое содержит угловой компонент:

машинопись:

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:

машинописи:

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); 
    } 

Проблема, как я узнал, это то, что единственный жизнеспособный способ для создания динамических компонентов заключается в использовании углов главного ViewContainerRef:

Но нет никаких документов или примеров, описывающих, как создать ViewContainerRef из динамически созданного элемента.


Можно ли каким-либо образом заставить инфраструктуру обрабатывать DOM? Как указано во многих потоках: «Угловой не обрабатывается innerHTML или appendChild». Это полный тупик?

Второе: возможно ли использование Renderer? (Не знакомый с этим), я видел это Canvas Renderer Experiment и теоретически, я думаю, он будет работать и с картой Google, так как мы можем экстраполировать, что карта - это просто особый вид холста. Доступна ли она в последней версии или она изменилась? DomRenderer не находится в документах, однако их можно найти в источниках.

ответ

9

Главное правило здесь - для динамического создания компонента, необходимого для его изготовления.

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) Unfortunatelly динамически созданный компонент не является частью изменения дерева обнаружения поэтому мы должны заботиться об обнаружении изменений. Это может быть сделано с помощью ApplicationRef.attachView(compRef.hostView), как было написано в приведенном выше примере, или мы можем сделать это с явной форме ngDoCheck (example) компонента, где мы создаем динамический компонент (AppComponent в моем случае)

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)); 
}); 
+0

Да, это помогает много, спасибо! –

+0

Рад слышать :) – yurzui

+0

Обнаружение изменений не работает для меня – Jigar

Смежные вопросы