The main rule: dynamically create a component to get its factory.
1) Add a dynamic component to the entryComponents array, except for inclusion in declarations :
@NgModule({ ... declarations: [ AppInfoWindowComponent, ... ], entryComponents: [ AppInfoWindowComponent, ... ], })
This is a tip for the angular compiler to create an ngfactory for a component, even if we are not using our component directly inside some template.
2) Now we need to introduce the ComponentFactoryResolver in our component / service, where we want to get ngfactory. You can think of ComponentFactoryResolver as storing factory components
app.component.ts
import { ComponentFactoryResolver } from '@angular/core' ... constructor(private resolver: ComponentFactoryResolver) {}
3) It's time to get the AppInfoWindowComponent factory:
const compFactory = this.resolver.resolveComponentFactory(AppInfoWindowComponent); this.compRef = compFactory.create(this.injector);
4) Having a factory, we are free to use it as we want. Here are some examples:
ViewContainerRef.createComponent(componentFactory,...) inserts a component next to viewContainer.
ComponentFactory.create(injector, projectableNodes?, rootSelectorOrNode?) Just creates a component, and this component can be inserted into an element that corresponds to rootSelectorOrNode
Note that we can provide a node or selector in the third parameter of the ComponentFactory.create function. This can be useful in many cases. In this example, I just create a component and then paste it into some element.
onMarkerClick might look like this:
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 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) Undoubtedly, a dynamically created component is not part of the change detection tree, so we also need to take care of detecting changes. This can be done using ApplicationRef.attachView(compRef.hostView) , as described in the example above, or we can do it using the ngDoCheck ( example ) component in which we create a dynamic component ( AppComponent in my case)
app.component.ts
ngDoCheck() { if(this.compRef) { this.compRef.changeDetectorRef.detectChanges() } }
This approach is better because it only updates the dynamic component when updating the current component. On the other hand, ApplicationRef.attachView(compRef.hostView) adds a change detector to the root of the change detector tree, and therefore it will be called with every change detection tick.
Plunger example
Tips:
Since addListener works outside the angular2 zone, we need to explicate our code inside the angular2 zone:
marker.addListener('click', (e) => { this.zone.run(() => this.onMarkerClick(marker, e)); });