medici
medici

Reputation: 199

navigator.geolocation.watchPosition return position unavailable

I have a service to monitor device position:

getLocation(opts): Observable<any> {
    return Observable.create(observer => {
        if (window.navigator && window.navigator.geolocation) {
            window.navigator.geolocation.watchPosition((position) => {
                observer.next(position);
            }, (error) => {
                switch (error.code) {
                    case 1:
                        observer.error('errors.location.permissionDenied');
                        break;
                    case 2:
                        observer.error('errors.location.positionUnavailable');
                        break;
                    case 3:
                        observer.error('errors.location.timeout');
                        break;
                }
            }, opts);
        } else {
            observer.error('errors.location.unsupportedBrowser');
        }
    });
}

then retrieve lat,long in component:

ngOnInit() {
    var source = this.locationService.getLocation({enableHighAccuracy:true, maximumAge:30000, timeout:27000});
    source.subscribe(pos => {
        this.lat = pos.coords.latitude;
        this.long = pos.coords.longitude;
    }, err => {
        this.err = err;
        console.log(err);
    });
}

this code work fine in browser on macbook and iphone i.e. it can retrieve and update position when device move.

but on my ipad(wifi only no gps) it can get position at first time then few second later, service return error code 2 i.e. position unavailable and browser stop update position. i'm not sure it's stop working or it's still running but alway return error code2.

My questions are:

  1. Does watchPosition need gps to work? but my macbook doesn't have gps too.
  2. If error occur and return from observable, do i need to resubscribe to get data(position) again or just wait until error gone.

Upvotes: 2

Views: 3451

Answers (2)

Blake Lassiter
Blake Lassiter

Reputation: 383

Here I am using the watchPosition function to get location from the user. Create a subject with a default location (optional). When the component loads, call getLocation and within your component, use an async pipe to display the data.

Live Demo

location.component.html

<div *ngIf="coordindates | async">
  <p>
    {{coordindates | async | json}}
  </p>
</div>

location.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import {
    LocationService
} from '@app/core';
import { Observable } from 'rxjs';

@Component({
    selector: 'app-location',
    templateUrl: './location.component.html',
    styleUrls: ['./location.component.scss']
})
export class LocationComponent implements OnInit {
    coordindates: Observable<Object>;
    constructor(private locationService: LocationService) { }

    ngOnInit(): void {
        this.coordindates = this.locationService.getLocation();
    }
}

location.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
@Injectable({
    providedIn: 'root'
})
export class LocationService {
    public watchId: number;
    public locationTrackingActive = false;
    public currentLoation: { latitude: number, longitude: number } = { latitude: undefined, longitude: undefined };
    private reactiveDeviceLocation$: Subject<Object>;

    constructor() {
        this.reactiveDeviceLocation$ = new BehaviorSubject<Object>(this.currentLoation);
    }

    getLocation(): Observable<Object> {
        const opts = { enableHighAccuracy: true, maximumAge: 60000, timeout: 30000 };
        this.watchId = navigator.geolocation.watchPosition((position) => {
            this.locationTrackingActive = true;
            this.currentLoation = { latitude: position.coords.latitude, longitude: position.coords.longitude };
            this.reactiveDeviceLocation$.next(this.currentLoation);
        },
        (err) => {
            console.log(err);
            this.locationTrackingActive = false;
        },
        opts);
        return this.reactiveDeviceLocation$;
    }
}

Upvotes: 2

Sam Rahimi
Sam Rahimi

Reputation: 106

There's a weird bug that I encountered in certain browsers using navigator.geolocation.watchPosition in an Angular2 service with RxJS, Meteor, and a bunch of other stuff. Not sure what the cause was, but "this" goes out of scope after the first time the callback is made and cannot be addressed in future callbacks. The fix is to create a locally scoped reference to the current class:

startTracking() {
    let that = this; //The fix            
    let opts = {maximumAge: 60000, timeout: 30000}
    this.watchId = navigator.geolocation.watchPosition((position) =>
        {
            console.log(position);
            that.locationTrackingActive = true;
            that.reactiveDeviceLocation.next(position.coords);
        },
        (err) =>
        {
            console.log(err);
            that.locationTrackingActive = false;
        },
        opts);

}

You do not need to resubscribe to watchPosition after an error, it keeps trying until it's able to get a signal. For your observable, using a BehaviorSubject (reactiveDeviceLocation above) lets you set a default starting location, then update it only on success... this way when a new client subscribes, they will be given the default (or most recent) location and can can work with that until watchPosition succeeds in updating the subject.

I set it up as follows (in the same service)

reactiveDeviceLocation: Rx.BehaviorSubject<Object> = new Rx.BehaviorSubject<Object>({latitude:37.5, longitude:122});

getReactiveDeviceLocation() {
    return this.reactiveDeviceLocation;
}

Any consumer of the service can then call

MyService.getReactiveDeviceLocation.subscribe((pos) => {
   console.log(pos); //Last known location.
})

Upvotes: 0

Related Questions