Dynamic Markers in Ionic 2

Dynamically Loading Markers with MongoDB in Ionic 2 – Part 2


In Part 1 of this tutorial series, we built a Node & Express server which uses MongoDB to store location data. We created a route that we could make a POST request to, and through supplying some data with that request it will return to us only locations that are within a certain proximity to the point we specify.

The purpose of this server is so that we can dynamically load in markers to our Google Map, such that only the markers that would currently be on screen are loaded – rather than loading and adding them all at once (which would lead to poor performance, and is unrealistic at large scales).

In this tutorial, we will be implementing the front end for the application in Ionic 2 that will make use of this simple API we have set up. Once we are done with this part, the application will be finished:

Dynamic Markers in Ionic 2

Before We Get Started

This tutorial assumes that you have already completed Dynamically Loading Markers with MongoDB and Ionic 2 – Part 1. This part will make use of the server we created in Part 1, so if you have not already completed that tutorial then you will not be able to complete this one.

1. Generate a New Ionic 2 Application

First, make sure that you are currently in the root folder for your project, not the server folder. Then create a new Ionic 2 application with the following command:

ionic start ionic2-dynamic-markers blank --v2

Once the application has finished generating, you may wish to rename the generated project folder from ionic2-dynamic-markers to client, however, that is not required.

You should now make the new Ionic 2 project your working directory, as we will need to create a provider.

Run the following command to create a GoogleMaps provider:

ionic g provider GoogleMaps

This provider will eventually contain all of the logic for setting up our Google Map and interacting with the server we have created. But first, we will need to set this provider up in our app.module.ts so that we are able to use it throughout the application.

Modify src/app/app.module.ts to reflect the following:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { GoogleMaps } from '../providers/google-maps';

  declarations: [
  imports: [
  bootstrap: [IonicApp],
  entryComponents: [
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, GoogleMaps]
export class AppModule {}

2. Implement the Google Maps Provider

This provider will handle the bulk of the logic for the application, so we are going to jump straight into building it. Once this is in place, the rest of the application will be quite simple to piece together.

Since it is quite a complex provider, we are just going to set up a skeleton of it first and then step through implementing it.

Modify src/providers/google-maps.ts to reflect the following:

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/map';

export class GoogleMaps {

    map: any;
    markers: any = [];

    constructor(public http: Http) {










    markerExists(lat, lng){


    getBoundingRadius(center, bounds){
        return this.getDistanceBetweenPoints(center, bounds.northEast, 'km');    

    getDistanceBetweenPoints(pos1, pos2, units){

        let earthRadius = {
            miles: 3958.8,
            km: 6371

        let R = earthRadius[units || 'miles'];
        let lat1 = pos1.lat;
        let lon1 = pos1.lng;
        let lat2 = pos2.lat;
        let lon2 = pos2.lng;

        let dLat = this.toRad((lat2 - lat1));
        let dLon = this.toRad((lon2 - lon1));
        let a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) *
        Math.sin(dLon / 2) *
        Math.sin(dLon / 2);
        let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        let d = R * c;

        return d;


        return x * Math.PI / 180;


This is the basic skeleton for our Google Maps provider. I’ve left out most of the functions, except a few helper functions at the end. These helper functions are responsible for calculating the distance between two points, which will allow us to calculate the distance between the center of the map and the edge of the map. By doing this, we will know the area in which we need to load markers for, and we can pass that information onto our server.

Let’s implement the remaining functions one by one now.

Modify the initMap function to reflect the following:


        let latLng = new google.maps.LatLng(-34.9290, 138.6010);

        let mapOptions = {
            center: latLng,
            zoom: 5,
            mapTypeId: google.maps.MapTypeId.ROADMAP

        this.map = new google.maps.Map(mapElement, mapOptions); 

        google.maps.event.addListenerOnce(this.map, 'idle', () => {


            google.maps.event.addListener(this.map, 'dragend', () => {



This function will be called from our home page and is what kicks off the whole process. It is responsible for creating a new Google Map, and it is also responsible for setting up two listeners. We set up a idle listener which will trigger the loadMarkers function as soon as the map has loaded for the first time (giving us our initial set of markers), and it also sets up a dragend listener which will also trigger loadMarkers every time the user moves the map. This is what will allow us to load in markers only as the user requires them.

Modify the loadMarkers function to reflect the following:


        let center = this.map.getCenter(),
            bounds = this.map.getBounds(),
            zoom = this.map.getZoom();

        // Convert to readable format
        let centerNorm = {
            lat: center.lat(),
            lng: center.lng()

        let boundsNorm = {
            northEast: {
                lat: bounds.getNorthEast().lat(),
                lng: bounds.getNorthEast().lng()
            southWest: {
                lat: bounds.getSouthWest().lat(),
                lng: bounds.getSouthWest().lng()

        let boundingRadius = this.getBoundingRadius(centerNorm, boundsNorm);

        let options = {
            lng: centerNorm.lng,
            lat: centerNorm.lat,
            maxDistance: boundingRadius



This function looks a little intimidating but it’s actually reasonably straightforward. We are using methods provided by the Google Maps JavaScript SDK to grab some information about the current status of the map, including the center point, zoom level, and the bounding coordinates (the North East corner of the map, and the South West corner of the map).

We then use this information to calculate the boundingRadius, which will be the distance between the center and the bound. It’s important that we calculate this value in terms of the latitude and longitude of those points, as the map can be viewed at a variety of zoom levels.

Modify the getMarkers function to reflect the following:


        let headers = new Headers();
        headers.append('Content-Type', 'application/json');

        this.http.post('http://localhost:8080/api/markers', JSON.stringify(options), {headers: headers})
            .map(res => res.json())
            .subscribe(markers => {




This is the function that actually makes a request to the server we created. We create a POST request to:


which is the route that we set up. We pass our options, which contains the center and bounds data, as the body of the request, and we set a JSON header on the request.

We are able to subscribe to this request (in fact, subscribing is required to kick off the request anyway) to receive the data that the server will return. Once the server returns the marker data, we pass it along to the addMarkers function.

Modify the addMarkers function to reflect the following:


        let marker;
        let markerLatLng;
        let lat;
        let lng;

        markers.forEach((marker) => {

            lat = marker.loc.coordinates[1];
            lng = marker.loc.coordinates[0];

            markerLatLng = new google.maps.LatLng(lat, lng);

            if(!this.markerExists(lat, lng)){

                marker = new google.maps.Marker({
                    map: this.map,
                    animation: google.maps.Animation.DROP,
                    position: markerLatLng

                let markerData = {
                    lat: lat,
                    lng: lng,
                    marker: marker





This function is responsible for adding all of the markers to the map, but before doing so it checks to see if a marker with the same coordinates has not already been added. If we did not perform this check first, markers would continually be readded to the map every time the user moves the map and triggers the loadMarkers function.

Modify the markerExists function to reflect the following:

    markerExists(lat, lng){

        let exists = false;

        this.markers.forEach((marker) => {
            if(marker.lat === lat && marker.lng === lng){
                exists = true;

        return exists;


This is the function that gets called to check if a marker already exists or not. It just loops through the markers array and checks for markers with matching coordinates.

3. Set up the Google Maps JavaScript SDK

We have the hard part out of the way now, but there are still a few more steps. Now we are going to set up the Google Maps JavaScript SDK in our project.

Modify src/index.html to include the following script:

<script src="https://maps.googleapis.com/maps/api/js"></script>

NOTE: If you are creating a production application, you will need to generate an API key with Google and supply it here.

By default, the Google Maps JavaScript SDK will cause us some issues with the TypeScript compiler since it won’t know what the google object is. To solve this, we will need to install the types for Google Maps.

Run the following command to install types for Google Maps

npm install @types/google-maps --save

4. Set up the Home Page

Now we just need to set up the map on our home page. I’ve explained how to set up Google Maps in-depth in this video so if you are unfamiliar with the process you may want to check that out, as I won’t be explaining it here.

Modify src/pages/home/home.ts to reflect the following:

import { Component, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';
import { GoogleMaps } from '../../providers/google-maps';

  selector: 'page-home',
  templateUrl: 'home.html'
export class HomePage {

    @ViewChild('map') mapElement;
    map: any;

    constructor(public navCtrl: NavController, public maps: GoogleMaps) {




Notice that we call the initMap function from our Google Maps provider here to kick off the whole process.

Modify src/pages/home/home.html to reflect the following:

      Dynamic Markers

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

Modify src/pages/home/home.scss to reflect the following:

page-home {

    #map {
        height: 100%;
        width: 100%;



That’s it! You should now be able to serve the application, scroll around the map, and watch markers load in on the fly. Just make sure that you are also running the server we created in Part 1 at the same time (by running node server.js) otherwise the HTTP request will fail.

Since we are loading markers in on the fly as required, and we are using a very efficient mechanism for storing and retrieving the location data (thanks to MongoDB and its GeoSpatial queries), we have a solution that is highly scalable.

What to watch next...

  • void brain

    maybe you want to modify it to work offline

  • antwerpenopen


    I get a MongoError(bad value): “invalid point in geo near query $geometry argument: { type: “Point”, coordinates: [ 138.601, 138.601 ] } longitude/latitude is out of bounds, lng: 138.601 lat: 138.601″

    Anyone knows what’s going on?


    • Jakob Mcclanahan

      For people also having trouble with this, in part 1 when creating the server, this line:
      “coordinates”: new Array(getRandomInRange(-180, 180, 3), getRandomInRange(-180, 180, 3))

      needs to be become
      “coordinates”: new Array(getRandomInRange(-180, 180, 3), getRandomInRange(-85, 85, 3))

      This is because the largest latitude possible is 85, by using 180 this will cause errors when generating the random points because a latitude of 180 or -180 simply doesn’t exist. This also causes the 2dsphere index not to get created, but an error is not thrown. If you run `db.markers.ensureIndex({location: ‘2dsphere’});` in your mongo command line you will see the error.

  • David Perreault

    I get this error below. Anybody got that and found a solution?

    “invalid argument in geo near query: $spherical”

    When executing the find below in Mongodb, I got the same error


    Error: error: {
    “ok” : 0,
    “errmsg” : “invalid argument in geo near query: $spherical”,
    “code” : 2,
    “codeName” : “BadValue”

    • Conor Mills

      I also get the same thing – did you manage to solve this?

      • Jakob Mcclanahan

        Sorry, this is so late, but $spherical is not part of the $near spec according to the docs, by simply removing that line it will work.

  • Shane

    How / where do you accommodate for the height of the toolbar and navbar? My markers are appearing before the screen even gets to the point.

  • Shane

    I seem to be having a problem with the http POST request.

    A GET request works fine, and the same POST from any other source also works, so there must be something wrong with the http POST method in ionic / angular.

    • Shane

      UPDATE: The only way I was able to get it to work was was with the following

      let body = new FormData();
      body.append('lat', data.lat);
      body.append('lng', data.lng);
      body.append('maxDistance', data.maxDistance);

      this.http.post(url, body)

      Let me know if anyone has found a cleaner / more dynamic way.

  • Shane

    Is there a reason you’re using `angular/http` instead of `cordova-plugin-http`? Is there any benefits you’re aware of?

  • misha130

    Hey might want to add a part about remove the markers cause it might be confusing for people

    public clearMarkers() {
    for (var i = 0; i < this.markers.length; i++) {
    this.markers = [];

  • Matt

    This guide what i looking for . just a question. how i can send gps location from another app and retrieve by this code and show locations .
    in another word . i need another code for sending location in background to this tutorial . how can help me ? im new to ionic

  • Shane

    Why grab the south west coordinates if you don’t use them anywhere? Were they intended to be used?

  • Shane

    Something else I don’t understand… Why does the bounding radius change with every pan? Shouldn’t it always stay relatively the same since the distance from the center of the screen to the corner never changes?

    • In regard to the south west coordinates they are not technically needed since we’re not using them in this case. We need to recalculate the distance from the center to the north west point , though, because if the user were to change the zoom level the distance on the screen from the center to the corner would of course remain the same, but the actual distance on the map would be larger or smaller.

  • mott

    Hi, how to add a marker pop-up info-window to this example , any help is much appreciated.

  • Jakob Mcclanahan

    I noticed that existing markers are re-added. If you add a console.log(lat, lng) to this `if(!this.markerExists(lat, lng)){}` you can see that the existing markers are continually re-added. I’m wondering if the markers array is being reset somehow. Any thoughts?

    • Jakob Mcclanahan

      This was a coding error on my part. The code in the tutorial works for my previous issue.

  • Benjamin Trotin

    How can i add listener on my makers ? Someone have an answer ? I’m trying to do some action with my markers but it doesn’t work.