Search code examples
angulartypescriptleafletngx-leaflet

ngx-leaflet does not detect layer changes through a service (Angular 6)


I am running Angular 6 and I want to dynamically add a layer to a leaflet map, but I can't detect changes in leafletLayers if the layers are modified from another component. Everything works fine if the layer changes are performed from map.component but it doesn't work if I make changes from outside:

Example:

map.component.ts

layers: Layer[];

addPolygon () {
    this.layers = [polygon([[ 43, 3 ], [ 42, 0 ], [ 44, 1 ]])]
}

map.component.html

<div class="map"
  leaflet
  (leafletMapReady)="onMapReady($event)"
  [leafletOptions]="options"
  [leafletLayers]="layers">
</div>
<div>
  <button type="button" name="button" (click)="addPolygon()"></button>
</div>

Everything works fine now, the polygon shows up as soon as I click the button. But I need that button to be in another component (form.component.ts), so I tried sharing the addPolygon() function with a service:

server.service.ts

@Injectable()
export class ServerService {
  addPolygon: Function;
}

I modified MapComponent and added the costructor for calling the service

map.component.ts

export class MapComponent{
  layers: Layer[];

  constructor(private serverService: ServerService){ 
     this.serverService.addPolygon = this.addPolygon 
  };

form.component.ts

export class FormComponent implements OnInit {
  constructor(private serverService: ServerService) { }

  triggerMap() {
      this.serverService.addPolygon()
  }
}

But If I bind triggerMap() on a button in form.component.html the polygon does not get added to the map. The console however says that addPolygon in map.component.ts is reached.

I tried even using NgZone, but it does not make any difference. Any suggestion?

Angular CLI: 6.0.8


Solution

  • I think I figured out your specific issue. It's not really specific to Angular or to Leaflet. I think it's more an artifact of Typescript and Javascript (or maybe something specific to how AOT compiling and TS->JS transpiling works).

    Basically, I think the issue is that when you assign the addPolygon function to the service object, the this context is changing. So, this ends up being the service's this and not the component this. The result is that a new layers array is created inside I'm assuming the service. You can (sort of) confirm this by doing the following in MapComponent:

    export class MapComponent {
    
      layers: Layer[] = [];
      counter = 0;
    
      constructor(
        private serverService: ServerService,
        private zone: NgZone
      ) {
        this.serverService.addPolygon = this.addPolygon;
      }
    
      options = {
          layers: [
              tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' })
          ],
          zoom: 5,
          center: latLng(42, 2)
      };
    
    
      addPolygon () {
        console.log('I am in addPolygon in MapComponent');
        this.counter++;
        console.log(`Here\'s the counter: ${this.counter}`);
    
        this.layers = [
            polygon( [[ 43, 3 ], [ 42, 0 ], [ 44, 1 ]] )
        ];
      }
    
      resetLayers () {
        console.log('Resetting layers');
        console.log(`Here\'s the counter: ${this.counter}`);
        this.layers = [];
      }
    
    }
    

    The idea here is that I'm modifying the local counter member variable inside of addPolygon and then I'm printing it to the console. I also print it to console inside of the resetLayers function.

    When I click the buttons in the following order: MapComponent, Reset, FormComponent, I get the following output:

    I am in addPolygon in MapComponent
    map.component.ts:36 Here's the counter: 1
    map.component.ts:44 Resetting layers
    map.component.ts:45 Here's the counter: 1
    map.component.ts:34 I am in addPolygon in MapComponent
    map.component.ts:36 Here's the counter: NaN
    

    MapComponent click increments the counter from 0 to 1 and prints it correctly with a value of 1. Reset click prints the correct counter value of 1. But, FormComponent click prints NaN because it isn't initialized to a value, so incrementing it leaves it as NaN.

    To try to nail down which this context is being used for FormComponent's call to addPolygon, I added counters initialized to different values for each of AppComponent, ServerService, and FormComponent. It turns out, it's calling addPolygon with the this context from ServerService.

    So, I think the solution to your issue is to either put the layers array inside of ServerServicem or its to use output bindings to communicate events from FormComponent to a component that is the parent of both MapComponent and FormComponent.