opening speech
In the process of working on a project on Angular 2 using maps, the following task arose: you need to render your Angulean component into a standard leaflet popup. In this article, the dynamic rendering of components will be considered in the context of this particular problem, but you can use this information in your own cases in the same way.
Formulation of the problem
The initial project is located by
reference . This is an angular 2+ application to which the library is connected to work with the leaflet.js map. In MapService there are methods for creating a map, adding markers to it and centering on markers. MapComponent - component to display the map. Webpack 2 is used to build the project. If you start the application, you will see a map with a marker with a popup of the following form:
marker.bindPopup(` <h3>Leaflet PopUp</h1> <p>Some text</p> <p *ngIf="false">Should be deleted from DOM if it was angular component because of ngIf = false<p> `);
Click on it and see the following picture:
In the DOM there is an element with the text “Should be deleted from DOM ...”, which I would like to delete using * ngIf, however in the popup you just cannot write the code of the angular to make it work right away. This is where the dynamic rendering of the angular components comes into play.
')
The solution of the problem
First, create the component that we want to dynamically render:
@Component({ selector: 'custom-popup', template: require('./custom-popup.component.html') }) export class CustomPopUpComponent { public inputData: any; private title: string = 'Angular component'; private array: Array<string> = ['this', 'array', 'was viewed', 'by', 'ngFor!']; }
His template:
<div> <h1>{{title}}</h1> <p>{{inputData}}</p> <p *ngFor="let text of array">{{text}}</p> <p *ngIf="false">Should be deleted from DOM if it was angular component because of ngIf = false</p> </div>
Next, create a new service dynamic-render.service.ts:
@Injectable() export class RenderService { private componentRef: ComponentRef<any>; constructor(private ngZone: NgZone, private injector: Injector, private appRef: ApplicationRef, private componentFactoryResolver: ComponentFactoryResolver) { } public attachCustomPopUpsToMap(map: Map) { this.ngZone.run(() => { map.on("popupopen", (e: any) => { const popup = e.popup; const compFactory = this.componentFactoryResolver.resolveComponentFactory(popup.options.popupComponentType); this.componentRef = compFactory.create(this.injector); this.componentRef.instance.geoObject = popup.options.object; this.appRef.attachView(this.componentRef.hostView); let div = document.createElement('div'); div.appendChild(this.componentRef.location.nativeElement); popup.setContent(div); }); }); } }
Since addListener is launched outside the angular area, we need to manually add it ourselves there. Thus, each time a popup is opened, a componentFactory is called, which creates a component that we have passed in the options field. Then we can use the instance of this component to write data into its fields, which we can also throw into the options popup. In this example, we assign the component inputData data from options.data. Then create a div element to which we attach our newly created component and assign it as content to the popup.
Note: this code is written for angular 2.3.0+. For earlier versions, this solution will look like this. Instead
this.appRef.attachView(this.componentRef.hostView);
will need to write
if (this.appRef['attachView']) { this.appRef['attachView'](this.componentRef.hostView); this.componentRef.onDestroy(() => { this.appRef['detachView'](this.componentRef.hostView); }); } else { this.appRef['registerChangeDetector'](this.componentRef.changeDetectorRef); this.componentRef.onDestroy(() => { this.appRef['unregisterChangeDetector'](this.componentRef.changeDetectorRef); }); }
Let's set the RenderService in the MapModule. You also need to add our CustomPopUpComponent to the MapModule in declarations and entryComponents. Call the renderService and add the ability for our map element to render Angulyar components in popup, then attach a custom component to the marker:
this.renderService.attachCustomPopUpsToMap(this.mapService.getMap()); let options = { data: 'you can provide here anything you want', popupComponentType: CustomPopUpComponent }; let myPopUp = L.popup(options); marker.bindPopup(myPopUp);
In the data field, proxy data for the component, in the popupComponentType, the component itself. This design can be wrapped in the interface for ease of use, but in this example we will not do this, the article is not about that. To display correctly, we will slightly correct the styles, after which we can launch the application. Clicking on the marker, we see that our component averaged in leaflet popup'e:

Conclusion
We managed to significantly expand the functionality of standard leaflet popups in conjunction with angular 2+. As a bonus, our components get an open / close animation, resize with zoom and other standard leaflet things.
The source code of the project in which everything described in the article is implemented is
here .