return-typeobservabletypescript1.7rxjs5

How to Return From Observable in TypeScript Method with Return Type


I have a Google geocode service in TypeScript. I need this method to return a "GoogleMap" class, after it fetches the lat/long for a given address.

I created a TypeScript method that returns a "GoogleMap" type. But, I'm getting a

function that is neither void nor any must return a value...

Here's my method:

getLatLongFromAddress(streetAddress: string): GoogleMap {

    this.geoCodeURL = GOOGLE_GEOCODE_BASE_URL + streetAddress +
        "&key=" + GOOGLE_MAPS_API_KEY;

    this.googleMap = new GoogleMap();

    this.httpService
        .get(this.geoCodeURL)
        .subscribe((data) => {
            this.googleMap.lat = data.results.geometry.location.lat;
            this.googleMap.long = data.results.geometry.location.lng;

            return this.googleMap;
        },
        (error) => {
            console.error(this.geoCodeURL + ".  " + error);

            return Observable.throw("System encountered an error: " + error);
        },
        () => {
            console.info("ok: " + this.geoCodeURL);

            return this.googleMap;
        });
}

I can understand the http call will be async and the flow ought to continue to the bottom of the method, possibly before the response returns data. To return a "GoogleMap", do I need to await this Observable? How do I go about doing this?

Thanks!

UPDATE: 4/21/16 I finally stumbled on an approach that I'm finding some satisfaction. I know there's a number of posts from developers begging for a "real" service. They want to pass a value to the service and get an object back. Instead, many of the answers don't fully solve the problem. The simplistic answer usually includes a subscribe() on the caller's side. The down-side to this pattern, not usually mentioned, is that you're having to map the raw data retrieved in the service in the caller's callback. It might be ok, if you only called this service from this one location in your code. But, ordinarily, you'll be calling the service from different places in your code. So, everytime, you'll map that object again and again in your caller's callback. What if you add a field to the object? Now, you have to hunt for all the callers in your code to update the mapping.

So, here's my approach. We can't get away from subscribe(), and we don't want to. In that vein, our service will return an Observable<T> with an observer that has our precious cargo. From the caller, we'll initialize a variable, Observable<T>, and it will get the service's Observable<T>. Next, we'll subscribe to this object. Finally, you get your "T"! from your service.

Take my example, now modified. Take note of the changes. First, our geocoding service:

getLatLongFromAddress(streetAddress: string): Observable<GoogleMap> {
  ...
  return Observable.create(observer => {
      this.httpService
          .get(this.geoCodeURL)
          .subscribe((data) => {
              ...
              observer.next(this.googleMap);
              observer.complete();
          }

So, we're wrapping the googleMap object inside the "observer". Let's look at the caller, now:

Add this property:

private _gMapObservable: Observable<GoogleMap>;

Caller:

getLatLongs(streetAddress: string) {
     this._gMapObservable = this.geoService.getLatLongFromAddress(this.streetAddr);

     this._gMapObservable.subscribe((data)=>{
          this.googleMap = data;
     });
}

If you notice, there's no mapping in the caller! you just get your object back. All the complex mapping logic is done in the service in one place. So code maintainability is enhanced. Hope this helps.


Solution

  • Your getLatLongFromAddress's signature says it will return a GoogleMap, however, nothing is ever returned (ie the return value of your function, as it stands, will be undefined).

    You can get rid of this compilation error by updating your method signature:

    // Return type is actually void, because nothing is returned by this function.
    getLatLongFromAddress(streetAddress: string): void {
    
        this.geoCodeURL = GOOGLE_GEOCODE_BASE_URL + streetAddress +
            "&key=" + GOOGLE_MAPS_API_KEY;
    
        this.httpService
            .get(this.geoCodeURL)
            .subscribe((data) => {
                this.googleMap = new GoogleMap();
                this.googleMap.lat = data.results.geometry.location.lat;
                this.googleMap.long = data.results.geometry.location.lng;
            },
            (error) => {
                console.error(this.geoCodeURL + ".  " + error);
                return Observable.throw("System encountered an error: " + error);
            },
            () => {
                console.info("ok: " + this.geoCodeURL);
                return this.googleMap;
            });
    }
    

    Additional tidbit, I don't think the onError and onComplete callback return values are used by Rx (looking at the documentation, the signature for these callbacks has a return value of void), although I could be mistaken.