Code review for flexible geolocation provider


#1

I’m new to Ionic and I’m buildling an app for both the browser and mobile. It relies on mapping so I’m writing a flexible Location provider to fetch the users location.

The idea is:

  • Get location from native Geolocation plugin if on a device
  • Get location from the browser navigator.geolocation web API if in the browser
  • Get location from an API endpoint (GeoIP) failing the above (not implemented yet)
  • Default to last know position failling the above
  • Default to a hard-coded value failing the above

I’d like to make sure I’m not overlooking anything obvious in the Ionic/Angular world, so I’d love some feedback on the approach I’m taking below.

import { Injectable } from '@angular/core';
import { Geolocation } from '@ionic-native/geolocation';
import { Platform } from 'ionic-angular';
import { Storage } from '@ionic/storage';

import { Connection } from './../../providers/connection/connection';

@Injectable()
export class Location {

  constructor(private platform: Platform, private geolocation: Geolocation,
              private storage: Storage, private connection: Connection) {
  }

  // Get location using Ionic Native Geolocation plugin
  private getDeviceLocation(): Promise<any> {
    return new Promise((resolve, reject) => {
      this.geolocation
        .getCurrentPosition()
        .then((pos) => {
          resolve({
            lat: pos.coords.latitude,
            lng: pos.coords.longitude,
          })
        })
        .catch(reject);
    });
  }

  // Get location using the browser API
  private getBrowserLocation(): Promise<any> {
    return new Promise((resolve, reject) => {
      if ('geolocation' in navigator) {
        navigator.geolocation.getCurrentPosition(
            (pos) => {
              resolve({
                lat: pos.coords.latitude,
                lng: pos.coords.longitude
              });
            },
            reject,
            {
              enableHighAccuracy: true,
              timeout: 5000,
              maximumAge: 0
            });
      } else {
        reject("Geolocation not available for this browser");
      }
    });
  }

  // Get location by calling external GeoIP API 
  private getGeoIPLocation(): Promise<any> {
    return new Promise((resolve, reject) => {
      if (this.connection.online) {
        reject('Not implemented');
      } else {
        reject("GeoIP location not available when offline");
      }
    });
  }

  // Get or set default location from storage
  private getStoredLocation(): Promise<any> {
    return new Promise((resolve, reject) => {
      Promise
        .all([
          this.storage.get('lat'),
          this.storage.get('lng')
        ])
        .then((values) => {
          let lat = values[0];
          let lng = values[1];
          if (!lat || !lng) {
            // TODO:
            // - can we get region information from device here?
            // - where do we store the default fallback lat/lng
            console.warn("No location saved in storage, fetching default location");
            lat = 53.350140;
            lng = -6.266155;
          }
          resolve({
            lat,
            lng
          });
        })
        .catch(reject)
    });
  }

  public getLocation(): Promise<any> {
    return new Promise((resolve, reject) => {

      let saveCoords = (coords) => {
        this.storage.set('lat', coords.lat);
        this.storage.set('lat', coords.lng);
        resolve(coords);
      }

      let getGeoLocation = this.platform.is('cordova') ? this.getDeviceLocation() : this.getBrowserLocation();
      getGeoLocation
        .then(saveCoords)
        .catch((err) => {
          console.warn('Device/browser geolocation failed, falling back to GeoIP. Reason:', err);
          this.getGeoIPLocation()
            .then(saveCoords)
            .catch((err) => {
              console.warn("GeoIP location failed, falling back to storage. Reason:", err);
              this.getStoredLocation()
                .then(saveCoords)
                .catch(reject);
            })
        });
    });
    
  }