Google Maps

Note: If you skipped ahead to this section, click here to download a zip of the tutorial at this point.

Let's add location to our parties.

The most popular maps widget is Google Maps so let's use that.

First, let's add the angular-google-maps Meteor package:

meteor npm install --save angular-google-maps

We also have to install another package:

meteor npm install --save angular-simple-logger

Then let's create a PartyMap component:

16.3 Create template for PartyMap imports/ui/components/partyMap/partyMap.html
1
2
3
4
5
<div class="angular-google-map-container">
  <ui-gmap-google-map center="partyMap.location || partyMap.map.center" events="partyMap.map.events" zoom="partyMap.map.zoom">
    <ui-gmap-marker coords="partyMap.location" options="partyMap.marker.options" events="partyMapmarker.events" idKey="party-location"></ui-gmap-marker>
  </ui-gmap-google-map>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import 'angular-simple-logger';
import 'angular-google-maps';
 
import template from './partyMap.html';
 
class PartyMap {
  constructor($scope) {
    'ngInject';
 
    this.map = {
      center: {
        latitude: 45,
        longitude: -73
      },
      zoom: 8,
      events: {}
    };
 
    this.marker = {
      options: {
        draggable: true
      },
      events: {}
    };
  }
}
 
const name = 'partyMap';
 
// create a module
export default angular.module(name, [
  angularMeteor,
  'nemLogging', // https://github.com/angular-ui/angular-google-maps/issues/1633
  'uiGmapgoogle-maps'
]).component(name, {
  template,
  controllerAs: name,
  bindings: {
    location: '='
  },
  controller: PartyMap
});

Here we created the google-map directive with attributes for binding the center, handling events and zoom of the map. We created the this.map variable to hold the properties on the map.

To display a Google Map widget we have to define it's height and width. Let's do that now. Create a new file named partyMap.css inside a the same folder as the component.

1
2
3
4
.angular-google-map-container {
  height: 400px;
  width: 400px;
}

We still have to import this file:

3
4
5
6
7
8
9
import 'angular-simple-logger';
import 'angular-google-maps';
 
import './partyMap.css';
import template from './partyMap.html';
 
class PartyMap {

Now we have to add it to the PartyDetails:

16.7 Add as a dependency to PartyDetails imports/ui/components/partyDetails/partyDetails.js
7
8
9
10
11
12
13
 
75
76
77
78
79
80
81
82
import template from './partyDetails.html';
import { Parties } from '../../../api/parties';
import { name as PartyUninvited } from '../partyUninvited/partyUninvited';
import { name as PartyMap } from '../partyMap/partyMap';
 
class PartyDetails {
  constructor($stateParams, $scope, $reactive) {
...some lines skipped...
export default angular.module(name, [
  angularMeteor,
  uiRouter,
  PartyUninvited,
  PartyMap
]).component(name, {
  template,
  controllerAs: name,
9
10
11
12
13
<button ui-sref="parties">Back</button>
 
<party-uninvited party="partyDetails.party" ng-show="partyDetails.canInvite()"></party-uninvited>
 
<party-map location="partyDetails.party.location"></party-map>

Now run the app and go to the party details page. You should see a new Google Map widget, but it doesn't do anything yet.

Let's add a marker that will be bound to the party's location.

Inside PartyMap template:

16.3 Create template for PartyMap imports/ui/components/partyMap/partyMap.html
1
2
3
4
5
<div class="angular-google-map-container">
  <ui-gmap-google-map center="partyMap.location || partyMap.map.center" events="partyMap.map.events" zoom="partyMap.map.zoom">
    <ui-gmap-marker coords="partyMap.location" options="partyMap.marker.options" events="partyMapmarker.events" idKey="party-location"></ui-gmap-marker>
  </ui-gmap-google-map>
</div>

The ui-gmap-marker directive represents a marker inside the map. We use the following attributes:

  • coords - where is the scope the marker location will be bound to.
  • options - object that holds the marker options. We are going to use the draggable option.
  • events - handling the events on the marker. We will use the click event.
  • idKey - where in the scope there exists the unique id of the object that the marker represent.

We already extended this.map variable to include handling those options.

Inside PartyMap component:

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
        longitude: -73
      },
      zoom: 8,
      events: {
        click: (mapModel, eventName, originalEventArgs) => {
          this.setLocation(originalEventArgs[0].latLng.lat(), originalEventArgs[0].latLng.lng());
          $scope.$apply();
        }
      }
    };
 
    this.marker = {
      options: {
        draggable: true
      },
      events: {
        dragend: (marker, eventName, args) => {
          this.setLocation(marker.getPosition().lat(), marker.getPosition().lng());
          $scope.$apply();
        }
      }
    };
  }
 
  setLocation(latitude, longitude) {
    this.location = {
      latitude,
      longitude
    };
  }
}

What happened here:

  • We created method to set a new value of location binding.
  • We added the click event to the map. Every time the user clicks the map, we take the location from the click event's params and save it as the party's new location.
  • We defined the options object under the marker to specify the marker is draggable.
  • We handled the dragend event that happens when the marker is dropped to a new location. We take the location from the event's params and save it as the party's new location.

Now is the time to use it in the PartyDetails.

Insert location value:

16.10 Add location to be updated with a party imports/ui/components/partyDetails/partyDetails.js
57
58
59
60
61
62
63
64
      $set: {
        name: this.party.name,
        description: this.party.description,
        public: this.party.public,
        location: this.party.location
      }
    }, (error) => {
      if (error) {

Again, with the great Meteor platform there is no need for sync or save function. We just set it and it syncs in all other clients.

Test it to see clicking and dragging works.

Multiple markers

Now let's add a map to the parties list to show all the parties on the map.

So let's create the PartiesMap component:

16.11 Create view for PartiesMap imports/ui/components/partiesMap/partiesMap.html
1
2
3
4
5
<div class="angular-google-map-container">
  <ui-gmap-google-map center="partiesMap.map.center" zoom="partiesMap.map.zoom">
    <ui-gmap-markers models="partiesMap.parties" coords="'location'" fit="true" idkey="'_id'" doRebuildAll="true"></ui-gmap-markers>
  </ui-gmap-google-map>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import angular from 'angular';
import angularMeteor from 'angular-meteor';
import 'angular-simple-logger';
import 'angular-google-maps';
 
import template from './partiesMap.html';
 
/**
 * PartiesMap component
 */
class PartiesMap {
  constructor() {
    this.map = {
      center: {
        latitude: 45,
        longitude: -73
      },
      zoom: 8
    };
  }
}
 
const name = 'partiesMap';
 
// create a module
export default angular.module(name, [
  angularMeteor,
  'nemLogging', // https://github.com/angular-ui/angular-google-maps/issues/1633
  'uiGmapgoogle-maps'
]).component(name, {
  template,
  controllerAs: name,
  bindings: {
    parties: '='
  },
  controller: PartiesMap
});

You can see that the difference between the directive we used in PartyMap is that ui-gmap-markers is plural.

The attributes we use:

  • models - the scope array that the markers represent.
  • coords - the property that holds the location.
  • click - handler for the click event on a marker
  • fit - a boolean to automatically zoom the map to fit all the markers inside
  • idKey - the property that holds the unique id of the array
  • doRebuildAll - a refresh option, will help us to refresh the markers in search

And use it in the PartiesList:

8
9
10
11
12
13
14
 
77
78
79
80
81
82
83
import template from './partiesList.html';
import { Parties } from '../../../api/parties';
import { name as PartiesSort } from '../partiesSort/partiesSort';
import { name as PartiesMap } from '../partiesMap/partiesMap';
import { name as PartyAdd } from '../partyAdd/partyAdd';
import { name as PartyRemove } from '../partyRemove/partyRemove';
import { name as PartyCreator } from '../partyCreator/partyCreator';
...some lines skipped...
  uiRouter,
  utilsPagination,
  PartiesSort,
  PartiesMap,
  PartyAdd,
  PartyRemove,
  PartyCreator,
31
32
33
34
35
36
</ul>
 
<dir-pagination-controls on-page-change="partiesList.pageChanged(newPageNumber)"></dir-pagination-controls>
 
 
<parties-map parties="partiesList.parties"></parties-map>

Depends on which version of google-maps you use, by now you might have encountered the following error message when trying to load the map component:

Oops! Something went wrong. This page didn't load Google Maps correctly. See the JavaScript console for technical details.

The map fails to load because in the newer versions of google-maps an API key is mandatory. An API key is a code passed in by computer programs calling an API to identify the calling program, its developer, or its user to the Web site. To generate an API key go to Google Maps API documentation page and follow the instructions. Each app should have it's own API key, as for now we can just use an API key we generated for the sake of this tutorial, but once you go production mode, replace the API key in the script below:

16.15 Add Google Maps API key client/index.html
1
2
3
4
5
6
<head>
  <base href="/">
  <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDbphq9crcdpecbseKX3Yx2LPxMRqWK-rc"></script>
</head>
<body>
  <socially></socially>

Summary

Run the app. Look at how little code we needed to add maps support to our app.

Angular 1 has a huge eco system full of great directives like the angular-google-maps one.