ngx-mapbox-gl icon indicating copy to clipboard operation
ngx-mapbox-gl copied to clipboard

Ng content not working

Open behuda opened this issue 6 years ago • 11 comments

base-map.html

<mgl-map #map[style]="'mapbox://styles/mapbox/streets-v9'"  [zoom]="[2]" [center]="[-103.59179687498357, 40.66995747013945]" (click)="onMapClick($event, map)"
  (load)="mapLoad($event)" (mouseMove)="onMapMouseMove($event, map)" [cursorStyle]="cursorStyle">
 
  <ng-content > </ng-content>

  <mgl-control mglGeocoder position="top-right"></mgl-control>
  
</mgl-map>

base-map.ts

import { Component,   } from '@angular/core';
import { MapMouseEvent } from 'mapbox-gl';

@Component({
  selector: 'base-map',
  templateUrl: 'base-map.component.html',
})

export class BaseMapComponent implements OnChanges {

  mapLoad() {  }
 
  onMapClick(evt: MapMouseEvent) {
    console.log(evt);
  }

  onMapMouseMove(evt: MapMouseEvent) {
    console.log(evt);
  }

}

point-map.html

<mgl-geojson-source id="mainSrc_cluster" [data]="mapGeoJSON" [cluster]="isCluster" [clusterMaxZoom]="14" [clusterRadius]="50">
    </mgl-geojson-source>
    <mgl-layer id="clusters" type="circle" source="mainSrc_cluster" [filter]="['has', 'point_count']" [paint]="{
          'circle-color': {
              property: 'point_count',
              type: 'interval',
              stops: [
                  [0, '#51bbd6'],
                  [100, '#f1f075'],
                  [750, '#f28cb1']
              ]
          },
          'circle-radius': {
              property: 'point_count',
              type: 'interval',
              stops: [
                  [0, 20],
                  [100, 30],
                  [750, 40]
              ]
          }
        }">
    </mgl-layer>

    <mgl-layer id="cluster-count" type="symbol" source="mainSrc_cluster" [filter]="['has', 'point_count']" [layout]="{
          'text-field': '{point_count}',
          'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
          'text-size': 12
        }">
    </mgl-layer>
    <mgl-layer id="unclustered-point" type="circle" source="mainSrc_cluster" (click)="onClick($event)" (mouseEnter)="cursorStyle = popupList.length > 0 || pointPopupHeader ? 'pointer' : ''"
      (mouseLeave)="cursorStyle = ''" [filter]="['!has', 'point_count']" [paint]="{
          'circle-color': unClusterPointColor,
          'circle-radius': [
            'interpolate', ['linear'], ['zoom'],
            10, 5,
            22, 20
          ],
          'circle-stroke-width': 1,
          'circle-stroke-color': '#fff'
        }">
    </mgl-layer>

similary i have file for lines.map.html, and have other files, containing layers and event handlers on that layer.

now in the main app file main-app.ts

 <base-map #map>
            <point-map  [mapData]="mapData" [mapMetaData]="mapMetaData"
            [mapComponent]="map.mapComp" 
              ></point-map>
</base-map>

the problem with approach is that im getting error ERROR Error: StaticInjectorError(AppModule)[LayerComponent -> MapService]:

also the element projected inside ng-content is called first in angular intitalization. so layer, and geojson component is initialzed before mgl-map component

behuda avatar Jul 30 '18 14:07 behuda

StaticInjectorError is solved by adding MapService in app.module(myApp) provider.

I have one more issue of: Due to ng-content GeoJSONSourceComponent ngOnInit is called before MapComponent ngOnInit (angular lifecycle method)

map.service.ts

private hookEvents(events: MapEvent) {
    this.mapInstance.on('load', () => {
      this.mapLoaded.next(undefined);

geojson-source.component.ts

ngOnInit() {
 this.MapService.mapLoaded$.subscribe(() => {
      this.MapService.addSource(this.id, {

Because of that nothing is plotted on map. if (!this.sourceAdded) is always false.

But later map onLoad would is fired, and mapLoaded.next(undefined), is pushed. in GeoJsonComponent mapLoaded event is subscribed to, but that subscription is not working. If that subscription work this.sourceAdded would be true and data would be plotted on map.

behuda avatar Jul 30 '18 18:07 behuda

You can't use ng-content in this case, because ng-content is for static content projection. To fulfill your use case, you can do dynamic content projection with ng-template. I can provide you an example if you want.

(Don't provide MapService to something else than a component, it won't work properly).

Wykks avatar Aug 16 '18 08:08 Wykks

Please, provide an example

behuda avatar Aug 20 '18 03:08 behuda

is there any possibility of integrating deck.gl in future

behuda avatar Aug 20 '18 03:08 behuda

@Wykks Did you provide the example somewhere yet? I would be interested in an example where you add the mapbox-gl-draw control to the map.

sroettering avatar Feb 05 '19 14:02 sroettering

@Wykks example ?

firashamila33 avatar May 25 '20 15:05 firashamila33

@Wykks I have attempted to use dynamic content injection but I still get the same StaticInjectorError. I am using an ng-template in my parent component and injecting it using ngTemplateOutlet.

May you share an example of dynamic content injection where this will work, please?

EvansMatthew97 avatar Jul 16 '20 13:07 EvansMatthew97

Guy, i think we need to ask it other way. Why do we need this capability at all? What use case we are trying to implement? Is there a way to implement it using existing functionality.

Please, everyone who is interested here. State your case. What exactly you are trying to achieve by injecting content into the map object?

dmytro-gokun avatar Jul 24 '20 07:07 dmytro-gokun

@dmytro-gokun Here's one use case. I have 2 maps on the same page that I want to bind to the same list of sources. However, I would prefer not to duplicate the logic and instead wrap it in a template. What I'd like to do is something like this:

<mgl-map #map1 ...>
  ...
  <ng-container [ngTemplateOutlet]="mapDataSourcesTemplate"
                [ngTemplateOutletContext]="{ dataSources: dataSources$ }">
  </ng-container>
  ...
</mgl-map>

<mgl-map #map2 ...>
  ...
  <ng-container [ngTemplateOutlet]="mapDataSourcesTemplate"
                [ngTemplateOutletContext]="{ dataSources: dataSources$ }">
  </ng-container>
  ...
</mgl-map>

<ng-template #mapDataSourcesTemplate
             let-dataSources="dataSources">
    <ng-container *ngFor="let dataSource of dataSources | async">
      <ng-container [ngSwitch]="dataSource.type">
        <mgl-geojson-source *ngSwitchCase="'GEOJSON'"
                            [id]="dataSource.id"
                            [data]="dataSource.data"></mgl-geojson-source>
        <mgl-image-source *ngSwitchCase="'IMAGE'"
                          [id]="dataSource.id"
                          [coordinates]="dataSource.coordinates"
                          [url]="dataSource.url"></mgl-image-source>
        ...
      </ng-container>
    </ng-container>
  </ng-template>

When I do, however, I get the following error:

NullInjectorError: R3InjectorError(AppModule)[MapService -> MapService -> MapService]: 
  NullInjectorError: No provider for MapService!

I can get rid of this error by adding MapService as a provider in AppModule. However, none of the sources get added to the map likely due to what @behuda outlined above.

AnthonyMacKinnon avatar Oct 08 '20 19:10 AnthonyMacKinnon

Any updates?

parabolabam avatar Jan 27 '21 18:01 parabolabam

Guy, i think we need to ask it other way. Why do we need this capability at all? What use case we are trying to implement? Is there a way to implement it using existing functionality.

Please, everyone who is interested here. State your case. What exactly you are trying to achieve by injecting content into the map object?

I think it might be very useful in case you have to display many maps in your project, thanks to that approach with content projection you can reduce configuration boilerplate, ease testing and still keep the flexibility in usage.

This is tempting and makes sense only if you have 2 or more maps in your project that's probably why content projection based solution came to my mind as well :)

aplaskota avatar Jul 24 '23 13:07 aplaskota