0

I have a map component utilizing the mapbox API that is dependent upon the longitudinal and latitudinal coordinates returned from a geocoder.

There is a service that calls an endpoint with some parameters. I subscribe to this service and use an arrow function as seen in the code below. While I expect the response to have been assigned to this.geocodeResponse as well as the respective this.longitude and this.latitude, these variables remain empty outside the function scope. I know this because I receive this response from the broswer ERROR Error: "Invalid LngLat object: (NaN, NaN)". I have corroborated this result by logging the responses to console.log and confirm that the three responses remain empty.

I know that the service works because it has been tested and produces a valid response elsewhere.

Here is the relevant code:

import { Component, OnInit, ViewChild, ElementRef, Input } from "@angular/core";

import mapboxgl from "mapbox-gl";

import { GeocodingService } from "../geocoding.service";
import { environment } from "../../../environments/environment";

@Component({
  selector: "app-dynamic-map",
  templateUrl: "./dynamic-map.component.html",
  styleUrls: ["./dynamic-map.component.css"]
})
export class DynamicMapComponent implements OnInit {
  @Input() queryString: String
  geocodeResponse: any;
  longitude: string;
  latitude: string;

  map: mapboxgl.Map;
  @ViewChild("mapElement") mapElement: ElementRef;
  constructor(private geocodingService: GeocodingService) {}

  ngOnInit() {
    mapboxgl.accessToken = environment.mapbox.accessToken;

    this.geocodingService.getGeoCords(this.queryString).subscribe(response => {
      this.geocodeResponse = response;
      this.longitude = this.geocodeResponse.features[0].bbox[0],
      this.latitude = this.geocodeResponse.features[0].bbox[1]
  });
  }

  /* Given a query string, get the first result of a places query from api and pass to
   * the dynamic map centering on coordinates */
  ngAfterViewInit() {

    /* THESE REMAIN UNASSIGNED HERE */
    console.log(this.geocodeResponse)
    console.log(this.longitude)
    console.log(this.latitude)

    this.map = new mapboxgl.Map({
      container: this.mapElement.nativeElement,
      style: "mapbox://styles/mapbox/streets-v9",
      center: [this.longitude, this.latitude],
      zoom: 12
    });
  }
}

I know the issue resides within the subscription to the service. I may not fully understand how Angular handles subscribe and the scoping of the response variable.

The expected behavior would be non-empty variables outside of the subscription response. Moreover, I have observed this behavior within the firefox debugger, but without the tags having been triggered within the debugger, the variables remain unassigned.

I can provide additional code and logging output if needed. Please be specific in what is needed to diagnose the problem. Furthermore, while an explicit resolution to the problem is nice, I would really like to understand why this problem is occurring because I have a few other components that have similar issues.

  • 1
    This happened because `.subscribe` is asynchronous function – joka00 Mar 12 '19 at 19:56
  • 1
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Igor Mar 12 '19 at 19:57

1 Answers1

0

The problem with the component is it relies on data that it first needs to receive from an asynchronous operation. There are 2 common ways to get around this:

  1. Initialize the critical values with default values in your component. When the component receives the response from the asynchronous call the values will be updated.
  2. Use an indicator in the component to check if the asynchronous response has been processed or not. Check this values where applicable in any typescript method calls and hide HTML in the template where the html relies on the values from the service.

Here is some code that illustrates the above points. Not all your code is duplicated below.

Default Values

export class DynamicMapComponent implements OnInit {
  longitude: string;
  latitude: string;

  constructor(private geocodingService: GeocodingService) {
    this.longitude = '0';
    this.latitude = '0';
  }

  ngOnInit() {
    mapboxgl.accessToken = environment.mapbox.accessToken;

    this.geocodingService.getGeoCords(this.queryString).subscribe(response => {
      this.geocodeResponse = response;
      this.longitude = this.geocodeResponse.features[0].bbox[0];
      this.latitude = this.geocodeResponse.features[0].bbox[1];
    });
  }

Indicator

export class DynamicMapComponent implements OnInit {
  longitude: string;
  latitude: string;
  isInitialized: boolean = false;

  constructor(private geocodingService: GeocodingService) {
  }

  ngOnInit() {
    mapboxgl.accessToken = environment.mapbox.accessToken;

    this.geocodingService.getGeoCords(this.queryString).subscribe(response => {
      this.geocodeResponse = response;
      this.longitude = this.geocodeResponse.features[0].bbox[0];
      this.latitude = this.geocodeResponse.features[0].bbox[1];
      this.isInitialized = true;
    });
  }

dynamic-map.component.html

<div *ngIf="isInitialized">
  <p>dependent html here</p>
</div>

See also How do I return the response from an asynchronous call? for more information about asynchronous calls in javascript.

Igor
  • 60,821
  • 10
  • 100
  • 175
  • Is there a way that I can force an update for solution (1)? The map is loading with the default coords, however longitude and latitude are never updated. –  Mar 14 '19 at 15:51
  • @Secaucus - you could but I do not know how you trigger it to load, it really depends on the API and if you can call it from javascript. I would check their documentation. Alternatively just use solution 2 and wait until you get a response from the api before you load the html for that control. – Igor Mar 14 '19 at 16:05