Menu

Using Promises and Services to load Async JS in AngularJS

I haven’t had the
time to post on my blog for a long while. Been pretty busy. Had to so
much to learn and so much to implement. Its a new year, 2015. Lots of
goals… need my grind face on. So today’s post is about Promises and
http dependent services in AngularJS application.

I am a fan of the
www.ionicframework.com .
It’s build on AngularJs and Cordova. It’s got pretty cool UI
components, JS and CSS. They also got a couple cool CLI utilities for
building hybrid cordova apps.

Back to business.

Todays #codefunk is
about wrapping XHR calls and non-blocking JS scripts using Promises
and services. The problem was, when building a recent location based
app, I had to use the HTML5 geolocation API and Google Maps. These,
all require external scripts to function meaning, they make Ajax
calls to other domains to load the scripts needed.

I needed to get the
device current location and load google maps. Usually, Ill just do
something like


add the google maps
URI in a script tag either at the <head> tag  or before the
closing </body> tag.

When I do try and
navigate to pages that need these resources, I might get undefined
objects because the scripts aren’t loaded into the DOM yet, even thou
angularjs has the view rendered.

So you get errors
like ….. is not defined, or …cannot find property of
undefined
…something like that from the browser’s console.

So first solution.
Fix for loading Google Maps .


// Google async initializer needs global function, so we use $window
angular.module('GoogleMapsInitializer')
.factory('Initializer', function($window, $q){

    //Google's url for async maps initialization accepting callback function
    var asyncUrl = 'https://maps.googleapis.com/maps/api/js?callback=',
        mapsDefer = $q.defer();

    //Callback function - resolving promise after maps successfully loaded
    $window.googleMapsInitialized = mapsDefer.resolve; // removed ()

    //Async loader
    var asyncLoad = function(asyncUrl, callbackName) {
      var script = document.createElement('script');
      //script.type = 'text/javascript';
      script.src = asyncUrl + callbackName;
      document.body.appendChild(script);
    };
    //Start loading google maps
    asyncLoad(asyncUrl, 'googleMapsInitialized');

    //Usage: Initializer.mapsInitialized.then(callback)
    return {
        mapsInitialized : mapsDefer.promise
    };
})

source from :
http://codereview.stackexchange.com/questions/59678/simple-async-google-maps-initializer-with-angularjs

Seemed to work well.
So to in my map service/ factory, I have something like

//var MapApp = angular.module('GoogleMapsInitializer', []);


//MapApp.factory('Initializer',

              Initializer.mapsInitialized
              .then(function(){
                  var myLatLng = new google.maps.LatLng(scope.center.lat, scope.center.lon);
                  var mapOptions = {
                    zoom: 17,
                    center: myLatLng,
                    mapTypeId: google.maps.MapTypeId.ROADMAP,
                    panControl: true,
                    zoomControl: true,
                    mapTypeControl: true,
                    scaleControl: false,
                    streetViewControl: false,
                    navigationControl: true,
                    disableDefaultUI: true,
                    overviewMapControl: true
                  };
                  scope.map = new google.maps.Map(element[0], mapOptions);
                  scope.myLocation = new google.maps.Marker({
                      position: myLatLng,
                      map: scope.map,
                      title: "My Location"
                  });

                  google.maps.event.addDomListener(element[0], 'mousedown', function(e) {
                    e.preventDefault();
                    return false;
                  });
                  infowindow = new google.maps.InfoWindow();
              });

HINT:: remember to pass in necessary dependecies.

2nd
Solution, needs a background knowledge on Promises ($q)

Alot of my ng-services for the app,
especially $http request required the latitude, longitude of the
device current position to be passed in as parameters. I use
ngCordova, which already has the
$cordovaGeolocation.getCurrentPosition() returning a promise. Using
that alone didnt quite work for me because I needed the promise
returned from $cordovaGeolocation.getCurrentPosition(posOption) to
resolve before I sent off my XHR ($http) request to my server.

So here is what  I
did. Have you used AngularJS UI router? Check it out. It as a feature
called resolve on the $stateProvider.state method. You can read up
what resolve does on the  AngularJS UI router docs.

So I had a resolve
property like this.

//code for resolve
on $state

  $stateProvider

    .state('app', {
      url: "/app",
      abstract: true,
      resolve: {
        geoPosition: function ($q, locationsService) {
            var q = $q.defer();
            var i = locationsService.geoLocationInit();
            i.then(
              function(position) {
                q.resolve(position);
                // $scope.position=position;
                // $scope.base = {
                //   lat: position.latitude,
                //   lon: position.longitude
                // };
              },
              function(e) {
                console.log("Error retrieving position " + e.code + " " + e.message);
              }
            );

            return q.promise;
        }
      }
    })

this way its, when
$q in the above code gets resolved, my controller gets initialized.
That means Ill have most likely always have the values for my
geoCoordinates in my services and controllers as an argument/
dependency to be injected into my controller as geoPosition .

Well leme start from
the top pretty quick so you have an idea of how I got the whole thing
working.

Ive got a service
whose geoLocationInit method returns a promise that gets fufilled
when the $cordovaGeolocation.getCurrentPosition()  promise gets
resolved. So any other controllers, services that require geo
position coordinates need to wait for
locationsService.geoLocationInit() to get resolved. Now, I believe a
Promise can only be fufilled / resolved once. Any other calls to the
a fufilled promise, gets its resolved value,

so here’s what’s
really going on in summarized code..

//in a service as a method of the returned object 
///..app.factory('locationsService',.....
    geoLocationInit: function (posOption) {
        var q = Q.defer();
        var self = this;
        posOption = posOption || ls_def_pos_option;
        $cordovaGeolocation.getCurrentPosition(posOption)
        .then(function (position) {
          self.setMyLocation(position.coords);
          q.resolve(position.coords);
        });

        return q.promise;
    },
//........................///
//in another service or controller
      locationsService.geoLocationInit()
      .then(function (geoPromise) {
        locationData.lon = self.getMyLocation().longitude;
        locationData.lat = self.getMyLocation().latitude;
        return q.resolve($http.get('/api/v1/position?' + $.param(locationData)));
      }, function () {
        return q.reject();
      });


HINT:: Always pass in your necessary dependencies

This way, no matter when the promise is resolved, any callbacks attached to its .then() method will get the resolved value ie. in my case the geolocation coordinates

Hey, I might miss or
get a few things wrong.. Please drop ur comments.. #codeFunk is all
about posting samples of code , suggestions and link from other
answers to help get a problem fixed.

No comments

Leave a Reply

Follow us

Best Reviews

  • No Posts

ADS