Mobile
Article

Creating a Location Sharing App Using the Ionic Framework

By Jay Raj

It’s hard to remember what travel was like before Google Maps. Fortunately battling with cumbersome, badly folded paper maps and hand drawn directions are a thing of the past. Instead, a mobile phone is slipped from a pocket to confirm the user location, the location desired and how to get between the two.

In this tutorial, I’ll show how to use Google Maps whilst developing mobile apps using the IONIC. Using this app the user will be able to mark a particular position on the map, fill in the address and save the location in a database. I’ll be creating a custom directive to integrate Google Maps into our app. I’ll be using Firebase to save the data.

The source code from this tutorial is available on GitHub.

The IONIC Framework

IONIC is a mobile application framework for developing hybrid apps using HTML5. It uses AngularJS to create rich and robust mobile applications.

From the official site,

Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components, gestures, and tools for building interactive apps. Built with Sass and optimized for AngularJS.

Getting Started

Start by installing Node.js. This will also install the node package manager npm.

Using npm install IONIC.

npm install -g cordova ionic

This tutorial teaches how to create a mobile app for the Android platform, so ensure the required dependencies are installed.

Once platform dependencies are installed create a blank IONIC project.

ionic start iMapApp blank

Navigate to the project directory iMapApp, add the required platform, build and emulate.

cd iMapApp
ionic platform add android
ionic build android
ionic emulate android

The blank app should be running in the Android emulator.

Running the app each time on the emulator would be a time consuming task, so use the IONIC cli to run the app in browser. Install the required dependencies using npm.

npm install

Once the dependencies are installed, run ionic serve in the terminal and the app should be running in the browser.

Creating the User Interface

Let’s start by adding a new template for displaying the map. Inside the project directory create a folder called www/templates. Inside templates create a file called map.html.

<ion-view title="iMap">
    <ion-content>

        <div>
            <div id="map">
            </div>

            <div width="80%" class="list list-inset" style="margin-left:10%;margin-right:10%;">
                <label class="item item-input">
                    <input type="text" ng-model="user.desc" placeholder="Description">
                </label>

                <button class="button button-full button-positive" ng-click="saveDetails()">
                    Save
                </button>
            </div>
        </div>

    </ion-content>
</ion-view>

Inside map.html there is a div called `#map’. The Google Map will be rendered here. Below the map is an input text box for the user to enter a description and a button to save the details.

We will make use of the ionNavView directive to render different templates based on different states. Let’s add the ionNavView directive to the www/index.html page. Remove the current contents of the body tag and above the ionNavView add the ionNavBar directive to create a top bar. Here is how the modified index.html should look:

<body ng-app="starter">

    <ion-nav-bar class="bar-positive">
    </ion-nav-bar>

    <ion-nav-view></ion-nav-view>

</body>

The title for the ionNavBar is set from the rendered ionView. As seen in the above map.html code, the title is set for the ionView.

IONIC uses the Angular UI router module to organize the app interfaces into different states. Let’s define a state for the map.html template. Open www/js/app.js and add the following code:

.config(function($stateProvider, $urlRouterProvider) {
    $stateProvider
        .state('map', {
            url: '/map',
            templateUrl: 'templates/map.html',
            controller: 'MapCtrl'
        })

    $urlRouterProvider.otherwise('/map');
});

The above code defines a new state for the URL, /map that will render the template map.html and be controlled by the MapCtrl controller (which will be defined shortly). $urlRouterProvider.otherwise('/map'); is used to set /map as the default state.

Inside www/js/ create a file called controller.js and add a reference in the www/index.html file.

<script src="js/controller.js"></script>

The controller code inside controller.js needs to be defined. Start by defining the angular module.

angular.module('starter.controllers', ['ionic'])

Define the controller MapCtrl.

.controller('MapCtrl', ['$scope', function($scope) {
// Code will be here
}]);

Inject the starter.controllers module into the starter app in js/app.js.

angular.module('starter', ['ionic','starter.controllers'])

Once saved, the map.html template is viewable.

App with empty map view

Next add the Google Map in map.html by creating a custom directive called map. This directive will be used as an attribute, so let’s start by defining the directive in controller.js.

.directive('map', function() {
    return {
        restrict: 'A',
        link:function(scope, element, attrs){
            // Code will be here
        }
    };
});

In map.html is a div #map. Let’s add the directive attribute to this.

<div id="map" map> </div>

Google Maps will require some default parameters like zoom, latitude, longitude etc. Pass these parameters to the directive:

<div id="map" lat="-23.639492" lng="133.709107" zoom="8" map>

</div>

These attributes can be accessed inside the directive’s link function using the parameter attrs.

.directive('map', function() {
    return {
        restrict: 'A',
        link:function(scope, element, attrs){

          var zValue = scope.$eval(attrs.zoom);
          var lat = scope.$eval(attrs.lat);
          var lng = scope.$eval(attrs.lng);

        }
    };
});

scope.$eval is used to evaluate the AngularJS expressions.

Next include the Google Maps API reference in index.html.

<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>

Define Google Map’s latitude and longitude using the default values.

var myLatlng = new google.maps.LatLng(lat,lng)

Define map options for the Google Map:

mapOptions = {
                  zoom: zValue,
                  center: myLatlng
                }

Define the map with the above mapOptions and bind it to the #map div which can be accessed by element[0].

map = new google.maps.Map(element[0],mapOptions)

Here is how the directive should now look:

.directive('map', function() {
    return {
        restrict: 'A',
        link:function(scope, element, attrs){

          var zValue = scope.$eval(attrs.zoom);
          var lat = scope.$eval(attrs.lat);
          var lng = scope.$eval(attrs.lng);


          var myLatlng = new google.maps.LatLng(lat,lng),
          mapOptions = {
                zoom: zValue,
                center: myLatlng
            },
              map = new google.maps.Map(element[0],mapOptions);


        }
    };
});

Add the following style to www/css/style.css to style the #map div.

#map{
    width:80%;
    height:400px;
    margin:10px auto;
    box-shadow:0 3px 25px black;
}

Save the above changes and Google Maps will be viewable on the map page.

Map visible in app

Let’s add a marker to the Google Maps.

marker = new google.maps.Marker({
      position: myLatlng,
      map: map,
      draggable:true
})

The default position of the marker is set as the latitude and longitude passed as an attribute and the draggable option is set as true. Here is the modified directive:

.directive('map', function() {
    return {
        restrict: 'A',
        link:function(scope, element, attrs){

          var zValue = scope.$eval(attrs.zoom);
          var lat = scope.$eval(attrs.lat);
          var lng = scope.$eval(attrs.lng);


          var myLatlng = new google.maps.LatLng(lat,lng),
          mapOptions = {
              zoom: zValue,
              center: myLatlng
          },
          map = new google.maps.Map(element[0],mapOptions),
          marker = new google.maps.Marker({
                position: myLatlng,
                map: map,
                draggable:true
          });


        }
    };
});

Save the above changes and there will be a draggable marker in the Google Maps.

Map with marker visible in application

Tracking Marker Position

Next we will attach a dragend event to the Google Maps marker to track the position of the marker. Inside the directive, add the following code to attach a drag end event listener:

google.maps.event.addListener(marker, 'dragend', function(evt){
        console.log('Current Latitude:',evt.latLng.lat(),'Current Longitude:',evt.latLng.lng());
});

Save the changes and try to drag the marker. Check the browser console and it should include the current latitude and longitude.

Saving the Details

Next we will define a $scope variable called user in MapCtrl. It will contain the current position latitude, longitude and the description entered by the user.

$scope.user = {};

Create a function called saveDetails in the MapCtrl controller. This will make use of the $scope.user variable to get the required data.

$scope.saveDetails = function(){
    var lat = $scope.user.latitude;
    var lgt = $scope.user.longitude;
    var des = $scope.user.desc;

    // Code to write to Firebase will be here
  }

When the user drags the marker on the map, update the $scope.user.latitude and $scope.user.longitude variables in the dragend event listener’s callback function.

google.maps.event.addListener(marker, 'dragend', function(evt){
    scope.$parent.user.latitude = evt.latLng.lat();
    scope.$parent.user.longitude = evt.latLng.lng();
    scope.$apply();
});

scope.$apply is called to update the model bindings. Attach a ngModel directive to the description input text box and a ngClick directive to the save button.

<label class="item item-input">
  <input type="text" ng-model="user.desc" placeholder="Description">
</label>

<button class="button button-full button-positive" ng-click="saveDetails()">Save</button>

Next we will save the data to firebase. Register for a free account with firebase if you haven’t already. Once logged in you should have a unique firebase URL. For example, my firebase URL is:

https://blistering-heat-2473.firebaseio.com

Sign in to your Firebase account and click on the plus link next to the URL in the dashboard. Enter the name as MapDetails and value as 0 to create a sub URL, /MapDetails.

Firebase Screenshot

Include the following script references in the index.html to use firebase in the app.

<script src="https://cdn.firebase.com/js/client/2.0.4/firebase.js"></script>

<script src="https://cdn.firebase.com/libs/angularfire/0.9.0/angularfire.min.js"></script>

Inject firebase in the starter.controllers module in controller.js.

angular.module('starter.controllers', ['ionic','firebase'])

Inject the $firebase module into the MapCtrl controller.

.controller('MapCtrl', ['$scope','$firebase', function($scope,$firebase)

Inside the MapCtrl create a firebase object using the firebase URL.

var firebaseObj = new Firebase("https://blistering-heat-2473.firebaseio.com/MapDetails");

Using firebaseObj create an instance of $firebase.

var fb = $firebase(firebaseObj);

Inside the saveDetails function, make use of the firebase push API to save data to firebase.

fb.$push({
    latitude: lat,
    longitude: lgt,
    description: des
}).then(function(ref) {
    $scope.user = {};
}, function(error) {
    console.log("Error:", error);
});

Save the above changes and refresh the app. Drag the marker to a preferred position, enter a description and click save. Check the firebase dashboard and the data should be there.

Once the data saves, include an alert to notify the user. Make use of the ionic popup to create it. Inject the $ionicPopup into the MapCtrl controller.

.controller('MapCtrl', ['$scope','$firebase','$ionicPopup', function($scope,$firebase,$ionicPopup)

Add a function called showAlert in the MapCtrl controller.

$scope.showAlert = function() {
    $ionicPopup.alert({
        title: 'iMapApp',
        template: 'Your location has been saved!!'
    });
};

The showAlert function will call the $ionicPopup service to show a pop up with a title and template. Call showAlert in the success callback of push API call.

fb.$push({
    latitude: lat,
    longitude: lgt,
    description: des
}).then(function(ref) {
    $scope.user = {};
    $scope.showAlert();
}, function(error) {
    console.log("Error:", error);
});

Save the changes and try to save the details again. Once the details are saved in firebase there will be a pop up with a success message.

Conclusion

In this tutorial, I demonstrated how to use Google Maps in an IONIC mobile app, specifically to create a custom directive to integrate Google Maps. For an in depth info on using IONIC framework, I would recommend reading the official docs or further IONIC tutorials on SitePoint.

Please add your thoughts, suggestions and corrections in the comments below.

  • Tiago Winehouse

    Nice!

  • http://coderexample.com Arkaprava Majumder

    Can I get github or other repo link to download this demo ?

  • Noel Hwande

    Thank you for the awesome tutorial. Do you have any suggestions on how to use the user’s current location as the default, rather than the hard-coded coordinates?

    • Abhay Amin

      you can use the GeoLocation cordova plugin to get the users current lat long, you just need to install the plugin and add some permissions to the platform’s manifest and config file, its all given in the Cordova documentation

      • Noel Hwande

        Thanks for the response.

  • Shu Xian

    why i keep getting this error “Error: Firebase.set failed: First argument contains undefined in property ‘latitude'” ? please help me :(

  • Jackson Coutinho

    Hi, is this possible without Firebase ????

    • segebee

      yes you can
      you could use localStorage

    • segebee

      yes you can
      you could use localStorage

  • segebee

    Great tutorial… loved it.

    If anyone has an issue with firebase not setting data, the culprit is the third parameter des which has no value.

    Give it a value in the controller

  • segebee

    Great tutorial… loved it.

    If anyone has an issue with firebase not setting data, the culprit is the third parameter des which has no value.

    Give it a value in the controller

  • Sean

    Thanks for such a good tutorial. Did anyone have an error with the controller map directive?, I remove the directive and UI appears, once you add it the screen becomes blank..

    • BMartin

      Hi Sean, I am having the same issue, have you found any resolution?

      Thanks in advance.

  • http://aksamaya.com Agus Apriliawan

    Very good tutorial.
    How to display the marker of longitude and latitude are saved in firebase?

    • aymen

      heey mate ! please if you get how to do that give me the answer or a helpful links .
      thank you :)

  • Sung Kim

    Thanks for the nice tutorial. It works fine in web (with ionic serve), but just blank in ios/android. Did I miss something?

    • Hussain Alaidarous

      Try using Gap debug, such a wonderful tool. Probably something wrong with request supporting JavaScript from http. Did you just download the github repo?

  • Greyson Piedade

    Please someone could help me to display the markers on the map , I will be very grateful for the help

  • Hussain Alaidarous

    Wow, seriously, this is the easiest tutorial I’ve ever been through. Hats Off to you Jay.

  • Hussain Alaidarous

    That means that you have a syntax or some other type of errors in your controller.

  • aymen

    hey mate ! nice tuto.
    but i want to know how to add the latitude and longitude in the map from the firebase (after pressing a button)
    thank you :)

  • Yudha Praditya

    great!!

  • Yudha Praditya

    hay jay, are you sure thath dont need Google map API?

  • Albert Higgins

    If you need to add an api key for more calls then replace the google api script in index.html with this (and putting in your google maps api key):

  • Varhaj Jocun

    finally! hi Jay, I have looked for this solution 3 days (and perfromed couple of others). That’s right! Very thanks.

  • Varhaj Jocun

    hi Jay, thanks for great tutorial … but I have some problem … I use your approach and add to load Latitude/Longitude values from my API through controller. There is my approach:
    My view:


    My controller has code:
    .controller(‘UserLocationMapCtrl’,[‘$scope’,’$http’,’BaseURL’,’$localStorage’,
    function($scope,$http,BaseURL,$localStorage) {
    var OnUserComplete=function(response) {
    $scope.UserPos=response.data;
    }
    $BuddyID=$localStorage.get(‘BuddyName’);
    $TempURL=’/API/UG/’+$BuddyID;
    $http.get(BaseURL.concat($TempURL))
    .then(function(response) {
    $scope.UserPos=response.data;
    $scope.Latitude=$scope.UserPos.UserPos[0].Lat;
    $scope.Longitude=$scope.UserPos.UserPos[0].Lng;
    });
    $scope.Zoom=16;
    $scope.UserLocation={};

    }])
    and my “directives.js” is pretty similar to your (from your great tutorial).
    But it seems, that view with google map wants to be render earlier than in controller I have values from API ($http.get is probably not complete/finished in this time yet).
    Therefore I have display view, but instead of map centered on particular GPS Lat/Lng position I have empty maps.
    On other side if I hardcode Lat/Lng values in controller in this way:

    $scope.Latitude=48.70937820070758;
    $scope.Longitude=21.260397017196624;
    $scope.Zoom=16;
    $scope.UserLocation={};

    }])
    everything running OK (maps is rendered and centered with this GPS position).
    Pls could you help me … how to use this google map (in view) with GPS position values loaded from API?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Mobile, once a week, for free.