Angular.js передает данные из асинхронной службы в область видимости

У меня есть сервис вроде

app.factory('geolocation', function ($rootScope, cordovaReady) {
    return {
        getCurrentPosition: cordovaReady(function (onSuccess, onError, options) {

            navigator.geolocation.getCurrentPosition(function () {
                var that = this,
                    args = arguments;

                if (onSuccess) {
                    $rootScope.$apply(function () {
                        onSuccess.apply(that, args);
                    });
                }
            }, function () {
                var that = this,
                    args = arguments;
                if (onError) {
                    $rootScope.$apply(function () {
                        onError.apply(that, args);
                    });
                }
            }, options);
        }),
        getCurrentCity: function (onSuccess, onError) {
            this.getCurrentPosition(function (position) {
                var geocoder = new google.maps.Geocoder();
                geocoder.geocode(options,function (results, status) {
                    var city = address_component.long_name;
                });
            });
        }
    }
});

И я хочу сделать из контроллера что-то вроде

function MainCtrl($scope, geolocation) {
   geolocation.getCurrentCity(function(city){
       $scope.city = city;
   });
};

Функция getCurrentPosition отлично работает, и город также определен, но я не знаю, как получить доступ к городу в контроллере.

Что происходит? Когда вызывается getCurrentCity, он вызывает getCurrentPosition для определения gps-коордов. Эти координаты передаются в качестве аргументов для метода onSuccess? Так что это совсем то же самое, что я хочу сделать в методе getCurrentCity, но я не знаю, как это сделать. Асинхронный геокодер извлекает город, я хочу применить новые данные к методу onSuccess.

Любые идеи?

Ответ 1

Вы имеете дело с обратными вызовами и асинхронным запросом. Поэтому вы должны использовать службу $q. Просто добавьте его в свою службу с помощью $rootScope и кордовой зависимости. И добавьте promises к вашей функции, подобной этой

getCurrentCity: function () {
    var deferred = $q.defer(); 
    this.getCurrentPosition(function (position) {
      var geocoder = new google.maps.Geocoder();
      geocoder.geocode(options,function (results, status) {
        var city = address_component.long_name;
        $rootScope.$apply(function(){
          deferred.resolve(city);
        });
      });
    });
    return deferred.promise;
}

И в вашем контроллере сделайте следующее, чтобы выполнить обещание.

function MainCtrl($scope, geolocation) {
   geolocation.getCurrentCity().then(function(result) {  //result === city
     $scope.city = result;
     //do whatever you want. This will be executed once city value is available
   });     
};

Ответ 2

Попробуйте это

function MainCtrl($scope, geolocation) {
   $scope.city = null;
   geolocation.getCurrentCity(function(city){
       $scope.city = city;
       if(!$scope.$$phase) {
            $scope.$digest($scope);
        }
   });
};

Иногда наблюдатель не всегда вызывается при инициализации контроллера, заставляя событие дайджеста, если область не находится в фазе, вы можете пойти туда, куда вы хотите пойти, я полагаю. Дайте мне знать, если я не понимаю ваш вопрос.

О, и я не знаю, правильно ли прочитал код, но кажется, что вы не вызываете обратный вызов onSuccess в своей функции: замените функцию getCurrentCity следующим образом:

getCurrentCity: function (onSuccess, onError) {
        this.getCurrentPosition(function (position) {
            var geocoder = new google.maps.Geocoder();
            geocoder.geocode(options,function (results, status) {
                var city = address_component.long_name;
                if(typeof onSuccess === 'function') {
                     onSuccess(city);
                }
            });
        });
    }

Ответ 3

Просто вызовите метод onSuccess в службе вместо обработки результата.

getCurrentCity: function (onSuccess, onError) {
    this.getCurrentPosition(function (position) {
        var geocoder = new google.maps.Geocoder();
        geocoder.geocode(options, onSuccess);
    });
}

И в вашем контроллере проанализируйте результаты и назначьте город:

function MainCtrl($scope, geolocation) {
   geolocation.getCurrentCity(function(results, status){
       // Parse results for the city - not sure what that object looks like
       $scope.city = results.city;
   });
};

Ответ 4

Исправьте меня, если я ошибаюсь, но представленное решение больше не будет работать полностью, поскольку более новые версии Angular ( > 1.2!?) больше не разворачивают $q promises автоматически.

Таким образом:

$scope.city = geolocation.getCurrentCity();

всегда будет обещанием. Поэтому вам всегда придется использовать:

geolocation.getCurrentCity().then(function(city) {
    $scope.city = city;
}