How to pop up Angular 4 component inside flyer popup?

I have been a user of Angular 1.x for a long time and am currently working on creating a new application using Angular 4. I still do not understand most of the concepts, but finally, I have something that really works well. However, I had a problem when I needed to display the Angular 4 component (although I only used directives in 1.x) in the Marker popup using the Leaflet.

Now in Angular 1.x I could just use $ compile for a template with a directive inside it ( '<component>{{ text }}</component>') with buttons and the like, and that would work, but Angular 4 was completely different in its AoT and compilation at runtime seems to be very complicated, and there is no easy solution for this.

I asked a question here , and the author says that I could use the directive. I'm not sure if this is the right approach or even how to mix my own code with its proposed solution ... so I made a small npm-based project with Angular 4 and Leaflet already set up in case you know how to help I or want try it (I really appreciate it!). I struggled about this, perhaps a week, and I was very tired of trying many alternatives without success :(

Here is the link to my repo on GitHub: https://github.com/darkguy2008/leaflet-angular4-issue

The idea is to create a PopupComponent (or something similar to it) inside a marker, whose code you can find in src / app / services / map.service.ts, line 38.

Thanks in advance! :)

EDIT

:) . , diff. , Angular 4 Leaflet : https://github.com/darkguy2008/leaflet-angular4-issue/commit/b5e3881ffc9889645f2ae7e65f4eed4d4db6779b

, GitHub. @yurzui! :)

+7
2

, @ghybs : D. Google ( ), , .

, entryComponents. m.onclick(), , div, div . , .

$ Angular 4. . @yurzui!:)

... (css, webpack ..) , OP, : https://github.com/darkguy2008/leaflet-angular4-issue, , :

import 'leaflet';
import './main.scss';
import "reflect-metadata";
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import { BrowserModule } from "@angular/platform-browser";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { Component, NgModule, ComponentRef, Injector, ApplicationRef, ComponentFactoryResolver, Injectable, NgZone } from "@angular/core";

// ###########################################
// App component
// ###########################################
@Component({
    selector: "app",
    template: `<section class="app"><map></map></section>`
})
class AppComponent { }

// ###########################################
// Popup component
// ###########################################
@Component({
    selector: "popup",
    template: `<section class="popup">Popup Component! :D {{ param }}</section>`
})
class PopupComponent { }

// ###########################################
// Leaflet map service
// ###########################################
@Injectable()
class MapService {

    map: any;
    baseMaps: any;
    markersLayer: any;

    public injector: Injector;
    public appRef: ApplicationRef;
    public resolver: ComponentFactoryResolver;
    public compRef: any;
    public component: any;

    counter: number;

    init(selector) {
        this.baseMaps = {
            CartoDB: L.tileLayer("http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png", {
                attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
            })
        };
        L.Icon.Default.imagePath = '.';
        L.Icon.Default.mergeOptions({
            iconUrl: require('leaflet/dist/images/marker-icon.png'),
            shadowUrl: require('leaflet/dist/images/marker-shadow.png')
        });
        this.map = L.map(selector);
        this.baseMaps.CartoDB.addTo(this.map);
        this.map.setView([51.505, -0.09], 13);

        this.markersLayer = new L.FeatureGroup(null);
        this.markersLayer.clearLayers();
        this.markersLayer.addTo(this.map);
    }

    addMarker() {
        var m = L.marker([51.510, -0.09]);
        m.bindTooltip('Angular 4 marker (PopupComponent)');
        m.bindPopup(null);
        m.on('click', (e) => {
            if (this.compRef) this.compRef.destroy();
            const compFactory = this.resolver.resolveComponentFactory(this.component);
            this.compRef = compFactory.create(this.injector);

            this.compRef.instance.param = 0;
            setInterval(() => this.compRef.instance.param++, 1000);

            this.appRef.attachView(this.compRef.hostView);
            this.compRef.onDestroy(() => {
                this.appRef.detachView(this.compRef.hostView);
            });
            let div = document.createElement('div');
            div.appendChild(this.compRef.location.nativeElement);
            m.setPopupContent(div);
        });
        this.markersLayer.addLayer(m);
        return m;
    }
}

// ###########################################
// Map component. These imports must be made
// here, they can't be in a service as they
// seem to depend on being loaded inside a
// component.
// ###########################################
@Component({
    selector: "map",
    template: `<section class="map"><div id="map"></div></section>`,
})
class MapComponent {

    marker: any;
    compRef: ComponentRef<PopupComponent>;

    constructor(
        private mapService: MapService,
        private injector: Injector,
        private appRef: ApplicationRef,
        private resolver: ComponentFactoryResolver
    ) { }

    ngOnInit() {
        this.mapService.init('map');
        this.mapService.component = PopupComponent;
        this.mapService.appRef = this.appRef;
        this.mapService.compRef = this.compRef;
        this.mapService.injector = this.injector;
        this.mapService.resolver = this.resolver;
        this.marker = this.mapService.addMarker();
    }
}

// ###########################################
// Main module
// ###########################################
@NgModule({
    imports: [
        BrowserModule
    ],
    providers: [
        MapService
    ],
    declarations: [
        AppComponent,
        MapComponent,
        PopupComponent
    ],
    entryComponents: [
        PopupComponent
    ],
    bootstrap: [AppComponent]
})
class AppModule { }

platformBrowserDynamic().bootstrapModule(AppModule);
+12
  1. , . , MycustomPopupComponent.
  2. app.module.ts . :
   entryComponents: [
      ...,
      MycustomPopupComponent
   ],
  1. :
constructor(
    ...
    private componentFactoryResolver: ComponentFactoryResolver,
    private injector: Injector
) {
  1. , .
private createCustomPopup() { 
    const factory = this.componentFactoryResolver.resolveComponentFactory(MycustomPopupComponent);
    const component = factory.create(this.injector);

    //Set the component inputs manually 
    component.instance.someinput1 = "example";
    component.instance.someinput2 = "example";

    //Subscribe to the components outputs manually (if any)        
    component.instance.someoutput.subscribe(() => console.log("output handler fired"));

    //Manually invoke change detection, automatic wont work, but this is Ok if the component does not change
    component.changeDetectorRef.detectChanges();

    return component.location.nativeElement;
}
  1. , Leaflet bindPopup. .
const marker = L.marker([latitude, longitude]).addTo(this.map);
marker.bindPopup(() => this.createCustomPopup()).openPopup();
0

Source: https://habr.com/ru/post/1681401/


All Articles