0

I am trying to push the value get from http request to a local declare variable userLat & userLng and access the variables from another function but getting undefined as result. The userLat & userLng was successfully retrieved. Tried using return this.userLat & this.userLng but failed, please point out if I make any mistake.

Previously work with using promised. Had alot of headache. Is there a simpler way to get the data?

Any help and suggestions are appreciated. Thanks in advance:)

map.ts

export class GoogleMapsProvider {

userLat:any;
userLng:any;

constructor(
public http: Http,
) {
}

load(){
   this.http.post()
  .map(res => res.json())
  .subscribe(data =>
  {  
      this.userDetails = data[0];

      this.userLat = this.userDetails.ListerLat;
      this.userLng = this.userDetails.ListerLng;

      console.log(this.userLat); // successfully return userLat
      console.log(this.userLng); // successfully return userLng

     //tried return this.userLat & this.userLng

  } 
}


calculate(locations) {

  console.log(this.userLat); //returned undefined
  console.log(this.userLng); //returned undefined

  let usersLocation = {
  lat: this.userLat,
  lng: this.userLng
};

}

using promise

    load(){

    if(this.data){
        return Promise.resolve(this.data);
    }

    return new Promise(resolve => {

        this.http.get('../example.json').map(res => res.json()).subscribe(data => {

this.data = data;

            resolve(this.data);
        });

    });

}
aaa
  • 857
  • 4
  • 25
  • 46
  • 2
    And how do you call `calculate` function? – MysterX Aug 07 '17 at 14:02
  • ok. sorry about that. Will take note in the future. I called the `calculate` function in another function. Previously i worked with hardcoded `userLat & userLng` it works fine. Now I want to apply it to dynamic data. – aaa Aug 07 '17 at 14:06
  • 1
    This seems like a scope issue to me, you should use let _self = this; inside your load method. And then set userLng via _self.userLng = value; – enf0rcer Aug 07 '17 at 14:06
  • 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) – Liam Aug 07 '17 at 14:07
  • 1
    @user3492940 no this is not related to scope; the fat arrow functions handle lexical scope the way you would hope. The problem here is all about asynchronous calls and having to realise that once you make an asynchronous call (e.g. `this.http.post()`) everything else has to acknowledge and work with that. So `load()` needs to save (or return) the observable or a promise, and `calculate` needs to wait for the results to become available which means it in turn needs to be asynchronous. – Duncan Aug 07 '17 at 14:12
  • @Duncan that should really be an answer imo. – thomasmeadows Aug 07 '17 at 14:16
  • @thomasmeadows as an answer I'd feel obliged to expand it with sample code showing how to actually do all that, so I'll leave it for someone else. – Duncan Aug 07 '17 at 14:20

1 Answers1

3

What you can do here is create an observable stream containing your latitude and longitude, and subscribe to that in your calculate function. This means that you never call calculate in your business logic, you only call it once when your service is instantiated.

Any time data comes back from the load method, your calculate logic will be triggered. You can trust that this will always happen when you get a response from your server, and you never have to manage the calculation invocation yourself.

So a few things:

  • Remove the latitude/longitude properties from your service.
  • Create an Observable stream that will contain the two values (latitude/longitude)
  • Subscribe to that stream immediately, so any time your load function adds values to the stream, calculations will be done.

Your service can look like this:

import { ReplaySubject } from 'rxjs/ReplaySubject';

export interface Location {
    latitude: number;
    longitude: number;
}

export class GoogleMapsProvider {
    private locationStream: ReplaySubject<Location> = new ReplaySubject();

    constructor(public http: Http) {
        // Subscribes to the location observable stream.
        this.calculate(); 
    }

    load(){
        this.http.post()
            .map(res => res.json())
            .subscribe(data => {  
                this.userDetails = data[0];

                // This will add the location values to the stream.
                this.locationStream.next({
                    latitude: this.userDetails.ListerLat,
                    longitude: this.userDetails.ListerLon
                });
            });
    }


    calculate() {
        // Any time the `load` adds values to the stream, the subscribe callback will be triggered.
        this.locationStream.asObservable().subscribe((location: Location) => {
            // Do some calculations using location.latitude and location.longitude
        });
    }
}

If you don't like the approach of using an Observable stream, you can still achieve this by making your calculate function pure.

So instead of accessing this.userLat and this.userLon, you pass in the location object so that the calculate function goes purely off of its inputs -- this way there can be no undefined values if you make sure you only call calculate with defined values.

The only problem with this approach, however, is that you cannot call calculate from outside of your load method without making the calculate method an impure function. And I would advise staying away from trying to keep state with the userLat and userLon properties in your service as that can be hard to debug as the complexity grows.

Nevertheless, here you go:

export interface Location {
    latitude: number;
    longitude: number;
}

export class GoogleMapsProvider {
    constructor(public http: Http) {}

    load() {
        this.http.post()
            .map(res => res.json())
            .subscribe(data => {  
                this.userDetails = data[0];

                // Call `calculate` here and pass in location object.
                this.calculate({
                    latitude: this.userDetails.ListerLat,
                    longitude: this.userDetails.ListerLon
                });
            });
    }


    calculate(location: Location) {
        // Do calculations with location.latitude and location.longitude
    }
}
Lansana Camara
  • 9,497
  • 11
  • 47
  • 87