# Ionic Extras Useful tools to add to an Ionic application. **You will need** * [Bower][bower] * [Cordova][cordova] * A running [Ionic][ionic] application (v1, with Angular 1) **Recommended reading** * [Ionic](../ionic/) --- ## Geolocation .breadcrumbs[
Ionic Extras
] The [HTML Geolocation API][html-geolocation] allows the user to provide their geographical location to web applications. Since an Ionic app is a web app, we can use it. You could use it directly, but there are also several Angular wrapper libraries ready to use. In this tutorial, we'll use [angularjs-geolocation][angularjs-geolocation]. Install it with Bower: ```bash $> bower install --save angularjs-geolocation ``` Add a ` ``` Also add the new Angular module as a dependency of your application in `www/js/app.js`: ```js angular.module('my-app', [ '...', `'geolocation'` ]) ``` --- ### Getting the user's location .breadcrumbs[
Ionic Extras
>
Geolocation
] Obtaining the user's current geographic coordinates is as simple as injecting the `geolocation` service in any of your controllers or services, and calling its `getLocation()` function: ```js angular.module('my-app').controller('MyCtrl', function(`geolocation`, $log) { var myCtrl = this; `geolocation.getLocation()`.then(function(`data`){ myCtrl.latitude = `data.coords.latitude`; myCtrl.longitude = `data.coords.longitude`; }).catch(function(err) { $log.error('Could not get location because: ' + err.message); }); }); ``` It's an **asynchronous** operation which returns a promise, so you call `.then()` to be notified when the location is available. You can also call `.catch()` to be notified if there's a problem retrieving the location. .grid-50[ When developing locally with `ionic serve`, the browser will ask for permission to get the user's location. Click **Allow**. ] .grid-50[
] --- ### Geolocation on insecure origins .breadcrumbs[
Ionic Extras
>
Geolocation
] If you get the following warning: ```txt *getCurrentPosition() and watchPosition() no longer work on insecure origins. *To use this feature, you should consider switching your application to a *secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details. ``` It's because your Ionic app is not running on localhost but on your IP address, and getting the user's location over unencrypted HTTP is **no longer allowed on insecure origins**. You should run your Ionic app on localhost to solve this issue: ```bash $> ionic serve --address localhost ``` --- ### Geolocation doesn't work on my device .breadcrumbs[
Ionic Extras
>
Geolocation
] When running on an actual mobile device, some versions of some platforms don't have the HTML geolocation API available. Install [cordova-plugin-geolocation][cordova-geolocation] to solve the issue. --- ## Leaflet .breadcrumbs[
Ionic Extras
] There are many JavaScript map libraries, each with their own advantages. For this tutorial, we'll use [Leaflet][leaflet] as it's one of the most popular, and it has a pretty good Angular library: [angular-leaflet-directive][angular-leaflet-directive]. You can install both with Bower (it will automatically install Leaflet as well): ```bash $> bower install --save angular-leaflet-directive ``` There are 3 files you need to include in `www/index.html`; Leaflet's stylesheet, and the JavaScript files for Leaflet and the Angular directive: ```html
``` Also add the `leaflet-directive` module as a dependency of your application in `www/js/app.js`: ```js angular.module('my-app', [ '...', `'leaflet-directive'` ]) ``` --- ### Your map state .breadcrumbs[
Ionic Extras
>
Leaflet
] We'll assume you have a **map state** defined with Angular UI router, and that this state has a template and a controller. It could look something like this: ```js .state('map', { url: '/map', controller: 'MapCtrl', controllerAs: 'mapCtrl', templateUrl: 'templates/map.html' }) ``` If you're using **tabs**, it could look like this instead: ```js .state('tab.map', { url: '/map', views: { 'tab-map': { controller: 'MapCtrl', controllerAs: 'mapCtrl', templateUrl: 'templates/map.html' } } }) ``` --- ### Adding a map .breadcrumbs[
Ionic Extras
>
Leaflet
] Let's attach some data to our controller. The map will need it: ```js angular.module('my-app').controller('MapCtrl', function() { var mapCtrl = this; * mapCtrl.defaults = {}; * mapCtrl.markers = []; * mapCtrl.center = { * lat: 51.48, * lng: 0, * zoom: 14 * }; }); ``` Use the `
` directive from the angular-leaflet-directive library to display a map in the template: ```html
*
*
``` --- #### Leaflet maps and Ionic .breadcrumbs[
Ionic Extras
>
Leaflet
>
Adding a map
] Be sure not to forget to add `scroll='false'` and `data-tap-disabled='true'` to your map's enclosing element: ```html
``` This prevents Ionic's mobile gestures from interfering with controlling the map. Also do not forget to give your map a width and height: ```html
``` --- ### Adding markers to your map .breadcrumbs[
Ionic Extras
>
Leaflet
] You can add a marker simply by adding it to the `mapCtrl.markers` array we defined earlier. Angular's two-way binding will do the rest: .grid-50[ ```js mapCtrl.markers.push({ lat: 51.48, lng: 0 }); ``` ] .grid-50[
] --- #### Marker tooltips .breadcrumbs[
Ionic Extras
>
Leaflet
>
Adding markers to your map
] If you want a tooltip to open when the marker is clicked, you can simply add a `message` property: .grid-50[ ```js mapCtrl.markers.push({ lat: 51.48, lng: 0, message: 'Hello World!' }); ``` ] .grid-50[
] --- #### Dynamic marker tooltips .breadcrumbs[
Ionic Extras
>
Leaflet
>
Adding markers to your map
] If you want to display dynamic data in the marker, you must create an Angular scope for the marker with `getMessageScope()`: .grid-50[ ```js var record = { title: 'Lorem ipsum' }; *var msg = '
Hello World!
'; *msg += '
{{ record.title }}
'; mapCtrl.markers.push({ lat: 51.48, lng: 0, message: `msg`, * getMessageScope: function() { * var scope = $scope.$new(); * scope.record = record; * return scope; * } }); ``` ] .grid-50[
] .container[ Do not forget to inject `$scope` in your controller for this example to work. ] --- #### Complex marker templates .breadcrumbs[
Ionic Extras
>
Leaflet
>
Adding markers to your map
] It's hard to maintain a marker template when you have to construct it manually like in the previous example. If your marker template becomes too complex, save it in a file like `templates/mapTooltip.html`. Then, when creating your marker, use the `ng-include` directive to load that template: ```js var record = { title: 'Lorem ipsum' }; mapCtrl.markers.push({ lat: 51.48, lng: 0, * message: '
', getMessageScope: function() { var scope = $scope.$new(); scope.record = record; return scope; } }); ``` --- ### Leaflet events .breadcrumbs[
Ionic Extras
>
Leaflet
] Leaflet broadcasts several [events][angular-leaflet-directive-events] on the Angular scope. You can react to them by injecting `$scope` and using its `$on()` function. For example, the `leafletDirectiveMap.dragend` event is fired after the user drags the map: ```js $scope.$on('`leafletDirectiveMap.dragend`', function(event, map){ console.log('Map was dragged'); }); ``` The `leafletDirectiveMarker.click` event is fired when the user clicks on a marker: ```js $scope.$on('`leafletDirectiveMarker.click`', function(event, marker) { var coords = marker.model.lng + '/' + marker.model.lat; console.log('Marker at ' + coords + ' was clicked'); }); ``` --- ## Mapbox .breadcrumbs[
Ionic Extras
] [Mapbox][mapbox] is a mapping platform you can use to design maps. You can integrate it into Leaflet to have a better looking map than the default one. Create an account, log in and go to the Mapbox studio:
--- ### Mapbox tilesets .breadcrumbs[
Ionic Extras
>
Mapbox
] Choose a **map tileset** for your app:
--- ### Mapbox map ID .breadcrumbs[
Ionic Extras
>
Mapbox
] Copy the **map ID** of your tileset, which you will need it to tell Leaflet to use it:
--- ### Mapbox access tokens .breadcrumbs[
Ionic Extras
>
Mapbox
] Go into your **Account** settings and generate a new API access token:
--- #### New mapbox access token .breadcrumbs[
Ionic Extras
>
Mapbox
>
Mapbox access tokens
] .grid-40[ You can leave the basic settings untouched and click on **Generate**: ] .grid-60[
] --- #### Copy the mapbox access token .breadcrumbs[
Ionic Extras
>
Mapbox
>
Mapbox access tokens
] Once you're done, copy the access token:
--- ### Using a Mapbox tileset with Leaflet .breadcrumbs[
Ionic Extras
>
Mapbox
] To use your Mapbox tileset with angular-leaflet-directive, you have to construct a **tile layer URL** containing both your **Mapbox tileset map ID** and your **Mapbox access token**: ```js var `mapboxMapId` = 'mapbox.satellite'; // Use your favorite tileset here var `mapboxAccessToken` = 'changeme'; // Use your access token here // Build the tile layer URL var mapboxTileLayerUrl = 'http://api.tiles.mapbox.com/v4/' + `mapboxMapId`; mapboxTileLayerUrl = mapboxTileLayerUrl + '/{z}/{x}/{y}.png'; mapboxTileLayerUrl = mapboxTileLayerUrl + '?access_token=' + `mapboxAccessToken`; ``` You can then add this URL to the Leaflet map's configuration object in the `mapCtrl.defaults` variable in your map controller: ```js mapCtrl.defaults = { tileLayer: mapboxTileLayerUrl }; ``` --- ### Mapbox and Leaflet .breadcrumbs[
Ionic Extras
>
Mapbox
] Your map should now be using your Mapbox tileset:
--- ## Using your mobile device's camera .breadcrumbs[
Ionic Extras
] The camera is part of your mobile device's hardware, so you'll need Cordova to access it for you. Install [cordova-plugin-camera][cordova-camera] with the following command in your app's directory: ```bash $> cd /path/to/projects/my-app $> cordova plugin add cordova-plugin-camera ``` This plugin adds a `navigator.camera` object that you can use to take pictures (only when running your app on an actual mobile device). --- ### Create an Angular service for the camera .breadcrumbs[
Ionic Extras
>
Using your mobile device's camera
] Instead of using the camera object directly, it's a good idea to put it into an Angular service. We'll create a `CameraService` service with a `getPicture()` function which returns a promise: ```js angular.module('my-app').factory(`'CameraService'`, function($q) { var service = { `getPicture`: function() { var deferred = $q.defer(); var options = { // Return the raw base64 PNG data destinationType: navigator.camera.DestinationType.DATA_URL, correctOrientation: true }; `navigator.camera.getPicture`(function(result) { deferred.resolve(result); }, function(err) { deferred.reject(err); }, options); return deferred.promise; } }; return service; }); ``` --- ### Calling the camera service .breadcrumbs[
Ionic Extras
>
Using your mobile device's camera
] You can now inject and use the `CameraService` in your controllers. Let's add a `takePicture()` function to a controller: ```js angular.module('my-app').controller('MyCtrl', function(`CameraService`, $log) { var myCtrl = this; `myCtrl.takePicture` = function() { `CameraService.getPicture()`.then(function(result) { $log.debug('Picture taken!'); `myCtrl.pictureData` = result; }).catch(function(err) { $log.error('Could not get picture because: ' + err.message); }); }; }); ``` Call it in the view and display the result: ```html
Take picture
``` --- ### The camera and `ionic serve` .breadcrumbs[
Ionic Extras
>
Using your mobile device's camera
] The camera and the `navigator.camera` object will only be available on physical devices, so your code will produce errors during local development with `ionic serve`. To mitigate this issue, you can add a function to your service to test if the camera is available: ```js angular.module('my-app').factory('CameraService', function($q) { var service = { * isSupported: function() { * return navigator.camera !== undefined; * }, // ... }; return service; }); ``` --- #### Notifying the user that the feature is unsupported .breadcrumbs[
Ionic Extras
>
Using your mobile device's camera
>
The camera and `ionic serve`
] That way you can display some kind of notification to tell the user that he won't be able to use the camera, for example using Ionic's `$ionicPopup` service: ```js .controller('MyCtrl', function(CameraService, `$ionicPopup`, $log) { var myCtrl = this; myCtrl.takePicture = function() { * if (!CameraService.isSupported()) { * return $ionicPopup.alert({ * title: 'Not supported', * template: 'You cannot use the camera on this platform' * }); * } // ... }; }); ``` --- ## Resources .breadcrumbs[
Ionic Extras
] **Documentation** * [angular-leaflet-directive][angular-leaflet-directive] ([examples][angular-leaflet-directive-examples]) * [cordova-plugin-camera][cordova-camera] * [cordova-plugin-geolocation][cordova-geolocation] * [Ionic v1 documentation][ionic-docs] [angular-leaflet-directive]: https://github.com/tombatossals/angular-leaflet-directive [angular-leaflet-directive-events]: http://tombatossals.github.io/angular-leaflet-directive/#!/examples/events [angular-leaflet-directive-examples]: http://tombatossals.github.io/angular-leaflet-directive/#!/examples/simple-map [angularjs-geolocation]: https://github.com/arunisrael/angularjs-geolocation/ [bower]: https://bower.io [cordova]: https://cordova.apache.org [cordova-camera]: https://github.com/apache/cordova-plugin-camera [cordova-geolocation]: https://github.com/apache/cordova-plugin-geolocation [ionic]: http://ionicframework.com [ionic-docs]: http://ionicframework.com/docs/v1/ [html-geolocation]: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/Using_geolocation [leaflet]: http://leafletjs.com [mapbox]: https://www.mapbox.com/