Ionic Maps

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



·

In a recent tutorial I showed you how to integrate Google Maps into an Ionic application, 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. 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

<?php

  //Create a connection to the database
  $mysqli = new mysqli("localhost", "username", "password", "database");

  //The default result to be output to the browser
  $result = "{'success':false}";

  //Select everything from the table containing the marker informaton
  $query = "SELECT * FROM table";

  //Run the query
  $dbresult = $mysqli->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, 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 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:

$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.

Create a new factory in app.js that looks like this:

.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:

Markers.getMarkers();

or

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:

.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:

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:

.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:

.controller('MapCtrl', function($scope, $state, $cordovaGeolocation) {

});

🙁

Create a new factory in app.js that looks like this:

.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 = "<h4>" + record.name + "</h4>";          

          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:

.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

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 is out now.

What to watch next...

  • Pingback: Integrating Google Maps with an Ionic Application | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • Pingback: Part 3: Advanced Google Maps Integration with Ionic and Remote Data | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • kanelaoka

    Hi…
    very good your tutorial,
    but I’m still learning ..

    I managed to get this far, but it does not show any point on the map

    I believe that my database is not correct.

    it would be possible to provide a database with some test data?

    Thank you so much

    • kanelaoka

      Hi..

      I had to modify your code to the file markers.php
      Change:
      $result = “{‘success’:true, ‘playgrounds’:” . json_encode($markers) . “}”;
      to:
      $result = ‘{“success”:true, “laygrounds”:’ . json_encode($markers) . ‘}’;

      now I see the debug he found my database

      unfortunately still I have an error

      TypeError: records is undefined

      for (var i = 0; i < records.length; i++) {

      I still can not replicate their map

      thanks

      • masai2k

        The code has another error:

        change:
        var records = markers.data.result;

        to:
        var records = markers.data.markers;

        and you had to modify the data inserted in the database! lat=120 don’t exists!!

        try: lat: 40.7274488 and lng: -73.9897746 for new york ……

      • Thanks for picking up those errors, I’ll update the post!

      • Ahmed Akermi

        masai2k
        yes sir it’s true thanks

  • Julio

    Hi thanks for the great tutorial, I’m trying to implement this importing the data from Firebase, any idea? thanks in advance!

  • Pingback: Part 1: Using the $http Service in Ionic to Dynamically Load Google Map Markers | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap | Aprender IONIC()

  • Luís Cunha

    The source code download for part 2 of the google map marker loading tutorial isn’t working and is sending some pdf file and a random config.xml.

    • Emil Eubboline

      The zoom factor would interest me too

  • Joseph Ruano

    How can I use firebase instead of php in this example?

  • Fernando Montesinos

    How can I display that same information from my DB like the name row inside a Tab in a list??

  • Agus Apriliawan

    I’ve tried the appropriate tutorial, but why there are no marker that appears?

  • Pingback: Ionic 2: How to Use Google Maps & Geolocation | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • I get an error in markers.php: Fatal error: Call to a member function fetch_array() on boolean in /…/ on line 16

    Have not made any changes to the code above. Just the database connection.

  • Domenico Carbone

    Hi, great tutorial!
    I have implemented it in an ionic cordova app but I have a problem:
    the map doesn’t load at start, it appear only if I refresh the page.
    Any suggestions?
    Thanks

    • LAKHMOR Fatiha

      Hi Domenico,
      Am having the same issue with ionic! did you manage to fix it yet?
      Thanks!

      • Domenico Carbone

        Hi,

        Googling I have not found any solution to fix the problem. I have solved using another script.

        https://gist.github.com/parth1020/4481893

        I put pieces of this script inside $cordovaGeolocation function. Now it works.
        I hope this is helpful.

      • LAKHMOR Fatiha

        Hi,

        Thank you for your reply, I used angular-google-maps api and it works well for me.

  • Maximiliano Morales

    Hi Josh, I’ve done your tutorial and it works perfect but I’m having a problem with infowindow I do not get to open one at a time. If you want I’ve a codepen (http://codepen.io/desarrollomaxijoga10/pen/xZxxoj) but it only have the services code in fact only the GoogleMaps factory. Hope you can help me. Regards

  • David Martianus Simarmata

    i’ve already followed the previous tutorial, everything works perfect. but when in this tutorial i get error ‘Cannot read property ‘offsetWidth’ of null’, Any suggestions? thanks

  • David Martianus Simarmata

    Hi, i get this error
    ‘TypeError: Cannot read property ‘length’ of undefined’ any suggestions? thanks

  • alexandre

    that great tutorial Josh!
    to improve the experience of the User with the map implemented MarkerClusterer for Google Maps v3
    http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/1.0/examples/simple_example.html

    code:

    .factory(‘GoogleMaps’, function($cordovaGeolocation, Markers){

    var apiKey = false;
    var map = null;
    var markerclusterer = null;
    var gmarkers = [];

    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(error);

    //Load the markers
    loadMarkers();
    });

    }

    // ———————————————————————–
    // This function picks up the click and opens the corresponding info window
    // ———————————————————————–
    function myclick(i) {
    google.maps.event.trigger(gmarkers[i], “click”);
    }

    function loadMarkers(){

    //Get all of the markers from our Markers factory
    Markers.getMarkers().then(function(markers){

    // console.log(“Markers: “, markers);

    var records = markers.data.markers;

    for (var i = 0; i < records.length; i++) {

    var record = records[i];
    var markerPos = new google.maps.LatLng(record.lat, record.lng);

    var image = {
    url: 'img/massage.png',
    // This marker is 20 pixels wide by 32 pixels high.
    size: new google.maps.Size(32, 32),
    // The origin for this image is (0, 0).
    origin: new google.maps.Point(0, 0),
    // The anchor for this image is the base of the flagpole at (0, 32).
    anchor: new google.maps.Point(32, 32)
    };
    // Add the markerto the map
    var marker = new google.maps.Marker({
    map: map,
    animation: google.maps.Animation.DROP,
    position: markerPos,
    icon: image,
    });

    var infoWindowContent = "” + record.name + “”;

    addInfoWindow(marker, infoWindowContent, record);

    }
    // create a Marker Clusterer that clusters markers
    markerCluster = new MarkerClusterer(map, gmarkers);

    });

    }

    function addInfoWindow(marker, message, record) {

    var infoWindow = new google.maps.InfoWindow({
    content: message
    });

    google.maps.event.addListener(marker, ‘click’, function () {
    infoWindow.open(map, marker);
    });
    // save the info (not used here)
    gmarkers.push(marker);
    }

    return {
    init: function(){
    initMap();
    }
    }

    })

  • kawthar

    it works for me only when i changed this line of code: var records = markers.data.result; to var records = markers.data.markers;

  • Simerjit

    Thanks. It works for me….

  • Hello There,
    Thanks for this tutorial! but i am getting a small problem
    The app works fine when I run it in the browser, but when I run it on a physical android device it does not show the map.
    Help would be appreciated 🙂

    • Shiv

      hello Pubudu I’m facing similar issue, did get the solution?

  • Emil Gabriel Kitua

    TypeError: Cannot read property ‘length’ of undefined… i get that error on the for loop code: for (var i = 0; i < records.length; i++) {…}

    how can i solve it?

  • Good!

  • Giovanni Abeni

    Very good tutorials man! Thank you!

  • valmy roi

    Please how can i customise the markers ? I want the user’s position to be green and the rest red. Thank you!!

  • edson

    excuse me why the map cant load without click F5 the function does not load the first time

  • Asterix

    I get Unexpected token ‘ in JSON at position 1. Why do you need to output ‘success’:true, ‘markers’: in markers.php?

    $result = “{‘success’:true, ‘markers’:” . json_encode($markers) . “}”;
    When I change to

    $result = “” . json_encode($markers) . “”;Then I don’t get any json errors, but of course the app doesn’t work then…