Part 2: Using the $http Service in Ionic 1.x to Dynamically Load Google Map Markers

12 min read

Originally published July 23, 2015

Check out my advanced Ionic tutorials at eliteionic.com

In a recent tutorial I showed you [how to integrate Google Maps into an Ionic application][1], if you haven't already read that tutorial I would recommend doing so now because we will be building on top of that in this tutorial.

We've already created a simple Google Maps display with a single marker and info window which is created wherever the users current position is. That in itself maybe useful in some cases: some applications may just be trying to show the location of a single event or business on a map for example.

A lot of applications that use Google Maps will be dynamic though, we might be mapping user submitted locations for example, and if we don't know where all the markers go when we create our application then the app we created in the last tutorial will not be suitable. Another issue is that if we have lots of markers, it's bad for performance to try and load them all at once (and the user doesn't need markers loaded in for London if they're only looking in Adelaide).

We'll be exploring how to deal with these two issues over a two part tutorial series. This tutorial will focus on loading in Google Maps markers dynamically using the $http service in Ionic, and in Part 2 we will create a way to only load markers for the area the user is currently looking at.

This tutorial will assume you have a database and server set up somewhere already that you can pull the markers from.

Fetching Google Maps Markers from a Database

In this scenario we are going to try loading some markers from a server, this means that you will need a server set up which can respond with the markers you wish to load. I'll be showing you how to do this specifically with PHP, but you could use anything you like as long as you can output some JSON data representing the markers you want to load.

Create a file called markers.php and save it somewhere accessible via http

Again, feel free to use something other than PHP if you like. You could either set this up on an actual server on the Internet, or simply through a local webserver like [XAMPP][2]. However you choose to host it, it should be accessible by going to a URL like http://example.com/markers.php or http://localhost/markers.php.

Now we're going to query a database to return the set of markers we wish to add to the map, and then output that result set to the browser.

Modify your markers.php file to reflect the following

query($query);

  //Build an array of markers from the result set
  $markers = array();

  while($row = $dbresult->fetch_array(MYSQLI_ASSOC)){

    $markers[] = array(
      'id' => $row['id'],
      'name' => $row['name'],
      'lat' => $row['lat'],
      'lng' => $row['lng']
    );
  }

  //If the query was executed successfully, create a JSON string containing the marker information
  if($dbresult){
    $result = "{'success':true, 'markers':" . json_encode($markers) . "}";        
  }
  else
  {
    $result = "{'success':false}";
  }

  //Set these headers to avoid any issues with cross origin resource sharing issues
  header('Access-Control-Allow-Origin: *');
  header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
  header('Access-Control-Allow-Headers: Content-Type,x-prototype-version,x-requested-with');

  //Output the result to the browser so that our Ionic application can see the data
  echo($result);

?>
```

In the code above we've established a connection to the database, and are running a query that will retrieve all the records we have in the `markers` table. The markers table will look something like this:

id name lat lng
1 Marker One 40.7274488 -73.9897746
2 Marker Two 42.7274488 -74.9897746
We're only returning four fields here: `id`, `name`, `lat`, `lng`. Obviously the most important onces to return are the latitude and longitude so that we can actually create a marker where necessary, but apart from that you can have anything you like. You might also want to return reviews for that location, or a description to add into the info window. Once we have retrieved those results we output them as a JSON string on the page. This is pretty simple right now since we're just returning the complete set of markers, but in the next tutorial we will modify this query to only return markers that would currently be visible to the user. Also, it's worth touching on the Cross Origin Resource Sharing (or CORS) issues I mention in the code above. You can [read more about CORS here][3], but essentially JavaScript by default can't make requests from one domain to another. If you're running an application through `localhost` and are trying to grab data from `http://www.example.com` then you could run into some trouble. CORS allows you to make these cross domain requests. ## Loading Google Map Markers Using the $http Service Now that we have a URL we can go that spits out some data giving us the latitude and longitude of our markers, we need a way to suck that into our Ionic application. We can do that using the [$http service][4] which is available in AngularJS (remember, Ionic is built on top of AngularJS). `$http` provides an easy way for us to get simple data from a URL, as well as posting to that URL as well. Requests made using the `$http` service will look something like this: ```javascript $http.get('http://example.com').then(function(response){ //the response from the server is now contained in 'response' }, function(error){ //there was an error fetching from the server }); ``` We ask the service to **get** data from a given URL, and then we use a **promise** in the form of `.then()` to handle what to do with that data when it is successfully retrieved. We use a **promise** because the response is not available right away, we first need to wait for it to load in from the server before we run the code, but we don't want to halt our entire application whilst it waits for this to load. The code we will create to do this will be pretty simple for now, and we _could_ just do it inside of a **controller**, but in general it is best to move as much of the heavy lifting away from the controller as possible. You should see controllers as being kind of like real life managers in a company: they're smart (hopefully), good at directing and telling people what to do but they don't like getting their hands dirty. So instead, we are going to create a **Factory** to handle loading this data. ### Creating a Factory We will be creating a **Factory** that can be injected into our **controller** to handle these `$http` requests. This follows that pattern of the controller controlling what is happening but not actually doing much of the work. You can do a lot of different things with a Factory in Ionic, but essentially a Factory will return an object when requested that will contain functions that allow you to do _stuff_. That description was super vague, I know, so if you want a little more information on Factories I recommend checking out [this post by Andrew McGivery on factories in Ionic][5]. > Create a new factory in **app.js** that looks like this: ```javascript .factory('Markers', function($http) { var markers = []; return { getMarkers: function(){ }, getMarker: function(id){ } } }) ``` We're not going to load this Factory into our controller just yet, but eventually we will have an object called `Markers` available in our controller and we will be able to access these functions in the factory through it by doing something like this: ```javascript Markers.getMarkers(); ``` or ```javascript Markers.getMarker(2); ``` The first would return all the markers available, and the second would return only the marker with an id of 2. Of course, neither of these will do anything right now because they don't contain any code, but hopefully you get the idea (also, we haven't implemented the server side code for returning a specific marker). We could add as many functions as we like to this Factory to do different things. But for now, let's just get the first one working. > Modify your `Markers` factory in **app.js** to reflect the following: ```javascript .factory('Markers', function($http) { var markers = []; return { getMarkers: function(){ return $http.get("http://example.com/markers.php").then(function(response){ markers = response; return markers; }); } } }) ``` Now the factory will make a `$http` request to the URL provided, it will take the JSON response it receives and give it back to our application in the form of `response`. We then assign that response to our `markers` array. Now if we were to try access this function of our Factory within our controller like this: ```javascript Markers.getMarkers(); ``` We would actually get some data back from it. We will be revisiting this Factory in the next part of this tutorial series, but for now it is done. So let's get to work on using that data in our Google Map. ### More Factories! Ok, so now we _could_ access the Markers factory in our controller by doing this: ```javascript .controller('MapCtrl', function($scope, $state, $cordovaGeolocation, Markers) { console.log(Markers.getMarkers()); }); ``` and we _could_ create the map in the controller as we did in the last tutorial and then use this data to create the markers on the map. But! Since we know how to use **factories** now, we should continue implementing that pattern of assuming the controller is lazy and abstracting work away from it. So instead of setting up Google Maps through our controller, we are going to create our own `GoogleMaps` factory. > Modify your **MapCtrl** controller in **app.js** to remove the Google Maps code we added in the last tutorial: ```javascript .controller('MapCtrl', function($scope, $state, $cordovaGeolocation) { }); ``` 🙁 > Create a new factory in **app.js** that looks like this: ```javascript .factory('GoogleMaps', function($cordovaGeolocation, Markers){ var apiKey = false; var map = null; function initMap(){ var options = {timeout: 10000, enableHighAccuracy: true}; $cordovaGeolocation.getCurrentPosition(options).then(function(position){ var latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); var mapOptions = { center: latLng, zoom: 15, mapTypeId: google.maps.MapTypeId.ROADMAP }; map = new google.maps.Map(document.getElementById("map"), mapOptions); //Wait until the map is loaded google.maps.event.addListenerOnce(map, 'idle', function(){ //Load the markers loadMarkers(); }); }, function(error){ console.log("Could not get location"); //Load the markers loadMarkers(); }); } function loadMarkers(){ //Get all of the markers from our Markers factory Markers.getMarkers().then(function(markers){ console.log("Markers: ", markers); var records = markers.data.result; for (var i = 0; i < records.length; i++) { var record = records[i]; var markerPos = new google.maps.LatLng(record.lat, record.lng); // Add the markerto the map var marker = new google.maps.Marker({ map: map, animation: google.maps.Animation.DROP, position: markerPos }); var infoWindowContent = "

" + record.name + "

"; addInfoWindow(marker, infoWindowContent, record); } }); } function addInfoWindow(marker, message, record) { var infoWindow = new google.maps.InfoWindow({ content: message }); google.maps.event.addListener(marker, 'click', function () { infoWindow.open(map, marker); }); } return { init: function(){ initMap(); } } }) ``` That's a big chunk of code and a lot bigger than our last factory, so I've added in comments to explain it a bit. Just like our other controller we are returning an object with some functions in it, or in this case just one function: `init()`. We've also added some private functions and variables in this Factory to do some behind the scenes work. So when we call `init()` it will run `initMap()` to initialise the Google Map, `loadMarkers()` to add the markers to it and `addInfoWindow()` to add info windows to the markers. Now when we want to create the Google Map, all we have to do is add one line inside of the `$ionicPlatform.ready()` function: ```javascript .run(function($ionicPlatform, GoogleMaps) { $ionicPlatform.ready(function() { // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard // for form inputs) if(window.cordova && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); } if(window.StatusBar) { StatusBar.styleDefault(); } GoogleMaps.init(); }); }) ``` Also note that I have injected the `GoogleMaps` factory at the top of this code. If you take a look at the application now, hopefully you see something like this (assuming you've got some markers in your database!): [
Ionic Map Markers
][6]
BONUS CONTENT: Grab the complete source code for this tutorial by entering your email below:

In the next tutorial we're going to add even more code to the Google Maps factory, which will dynamically load the map based on whether the user is connected to the Internet or not, only load markers that are currently on screen and more cool stuff. **UPDATE:** [Part 3][7] is out now. [1]: http://www.joshmorony.com/integrating-google-maps-with-an-ionic-application/ [2]: https://www.apachefriends.org/index.html [3]: http://enable-cors.org/ [4]: https://docs.angularjs.org/api/ng/service/$http [5]: http://mcgivery.com/ionic-using-factories-and-web-services-for-dynamic-data/ [6]: https://www.joshmorony.com/media/2015/07/Screenshot-2015-07-23-20.04.32.png [7]: http://www.joshmorony.com/part-3-advanced-google-maps-integration-with-ionic-and-remote-data/