This tutorial is out of date and no longer maintained.
“MEAN Apps with Google Maps” (A tongue twister to be true).
And yet, whether you’re building an application to visualize bike lanes in your city, designing a tool to chart oil wells across the globe, or are simply creating an app to help choose your next date – having access to interactive, data-rich maps can be a critical asset. Thus, in this two-part tutorial, we’ll be writing code that directly integrates Google Maps with the views, controllers, and data of a MEAN-based application.
In Part I of the tutorial, we’ll be building the initial interface and data-binding the HTML and Angular elements with MongoDB and Google Maps. In Part II of the tutorial, we’ll be utilizing MongoDB’s geospatial and other querying tools to create complex filters on the map itself.
As you follow along, feel encouraged to grab the source code in the link provided. Just note: The code found in the GitHub link has a few additional tweaks compared to today’s tutorial. To get an exact replica of what we’re building today, use the TutorialMaterial/PartI link.
As the title clearly suggests, we’ll be using the Google Maps JavaScript API throughout. The API is richly documented, easy to learn, and free for low-volume usage. It may be worth flipping through the documentation guides now to see what’s possible. Once you’re ready to begin, head to the API homepage and sign-up to “Get a Key”. Simply follow the instructions for creating a project and you will be granted a unique API key. Hang onto this key!
Once you have your key, head to the APIs section of the Google Developer Console and click the link to the Google Maps JavaScript API. Make sure the API is Enabled under your new Project. (If it says: “Disable API” then you’re good).
The final product we’ll build is a basic two-panel application. On the left is a map and on the right is a control panel. In this first part of the tutorial, the control panel will be used to add new users to the map. (In the second part, the control panel will also be used to filter the map results).
Before we start working, go ahead and create an app directory as follows:
MapApp
-- app // Backend
---- model.js
---- routes.js
-- public // Frontend
---- index.html
---- js
------ app.js
------ addCtrl.js
------ gservice.js
---- style.css
-- server.js // Express Server
-- package.json
We’ll try to keep things simple throughout. The app will be composed of three sections:
Once you’re ready, run the following commands in your terminal to grab the necessary dependencies from bower:
bower install angular-route#1.4.6
bower install angularjs-geolocation#0.1.1
bower install bootstrap#3.3.5
bower install modernizr#3.0.0
Next, add the following content to your package.json
file
Then, run npm install
to install the relevant node packages.
Now, replace the content of your index.html
file with the content below. There’s nothing too fancy here, so just paste and wait for the explanation after.
At this point, we have a basic HTML page with a div for a map and a div with an HTML form for adding users. That said, there are a few things to note:
ng-app=meanMapApp
at the top of the page. We’ll use this to reference our Angular module later on.holderjs
image in place of our map for now. This will let us get a quick visual of what we have.noValidate
attribute in the HTML form element. This disables HTML form validation. Instead, we’ll be using Angular to validate our form.ng-model
throughout the form. Each of these takes content in a textbox or control element and uses it to set the value of an associated property in the scope variable formData
. So if a user sets their username to be “ExampleUser”, then the variable $scope.formData.username
would equal “ExampleUser” as well.ng-disabled="addForm.$invalid"
. This will prevent a user from clicking the Submit button unless the form is completely valid. In our case, since all fields have the attribute of required
, this means that all fields will need to be populated before the button is enabled.See. Nothing fancy!
But at this point, you should be able to do a quick browser inspection.
Now that we have an initial HTML template, it’s time to create the Node and Express server that will handle GET and POST requests for data. Paste the below code in the server.js
file. This code is a great template for building quick express servers, so keep it handy. It includes morgan for handling request logs, body-parser
for parsing JSON POST bodies, and specifies the location of the index.html
file and bower_components
.
Worth noting is that the server is configured to use localhost:3000
in displaying the app and that we’ll be connecting to a local instance of MongoDB. (Remember, since we’re running this in localhost
to always initiate Mongod
during testing).
Boot up mongod
in the terminal. Then run a quick test of the server using the command node server.js
in your terminal window. If all goes well, you should see our earlier HTML content when you navigate to localhost:3000
in your browser.
Next up, let’s create a Mongoose Schema that we can use to interact with the user data we’ll be dumping into MongoDB. Navigate to your model.js
file and paste the following code
Here we’ve established the structure we’ll be expecting (and enforcing) our user JSON to maintain. As you can see, we’re expecting six different fields:
Username
,Favorite language
,html5 verified
We’ve also created pre-save logic which initially sets the created_at
and updated_at
fields equal to the datetime of insertion.
Importantly, we’ve also established that the UserSchema
should be indexed using a 2dsphere
approach. This line is critical because it allows MongoDB and Mongoose to run geospatial queries on our user data. This means being able to query users based on geographic inclusion, intersection, and proximity. Check out the reference docs on 2dsphere indexes for more information. As an example, we’ll be using the $near query condition in Part II to identify users that fall within so many miles of a given location.
Also, important is the fact that MongoDB requires coordinates to be ordered in [Long, Lat]
format. (Backwards-seeming. I know. But very important.) This is especially important to remember because Google Maps requires coordinates in the other direction [Lat, Long]
. Just try to keep things straight as you’re working.
Finally, the model.js
file ends, with us exporting the Mongoose model and establishing a MongoDB collection of scotch-users
as the holding location for our data. (Note: “scotch-users” isn’t a typo. Mongoose adds an extra letter ‘s’ when creating collections).
We’re making great progress! Next up, we need to create the Express routes for retrieving and creating new users in our MongoDB database. For the purpose of this tutorial, we’re going to create the bare minimum routes: one route to retrieve a list of all users (GET) and one route to add new users (POST). Paste the below code in your routes.js
file to set this up.
Then, in your server.js
file, uncomment the line associated with routing to connect our routes to the server:
Things to note here:
var newuser = new User(req.body);
and retrieve all users with the line var query = User.find({});
. (Note: These users are NOT yet added to our map. We’re just dealing with the MongoDB database at this point.)/user
for both the GET and POST requests. At any point during testing, we can direct our browser to localhost:3000/users
to see what’s in the database.Speaking of testing, now’s a good time to test the routes. Let’s open up Postman (or a similar HTTP testing client) and run two tests.
First, let’s run a POST request to create a new user. Paste the below content into the body of a POST request and send it to localhost:3000/users
. Remember to set the content type to “Raw” and “JSON (application/json)” before sending.
Once you’ve sent the request, navigate your browser to localhost:3000/users
. If all went well, you should see the JSON you just sent, displayed before you.
Huzzah!
We’re slowly but surely clawing forward. Now it’s time to bring in Angular. First things first, let’s declare the initial angular module in public->js->app.js
. This file will serve as the starting Angular module. Once again, let’s keep it simple.
For now, the module will pull only from addCtrl
(the controller for our Add User Form) and geolocation, a module we downloaded earlier through Bower. We’ll be using the ‘Geolocation’ module to provide a user’s HTML5 verified location later.
Next, let’s open up the addCtrl.js
file and begin creating our controller. Let’s begin with the basics: creating the function needed to add users to our database.
The logic here is straightforward if you’ve worked with Angular before, but for those who haven’t, the code essentially refers back to each of the textboxes and control elements using the $scope.formData.VAR
format. These are initially set to blanks (except location, which has initial dummy numbers).
Once a user hits a button associated with the createUser()
function, the Angular controller initiates a process of grabbing each of the textbox and control values and storing them in the object userData
. From there, an HTTP post request is made to the '/users
route we created earlier. The form is cleared (except for location) and the function completes.
The next step is for us to return to our index.html
file and attach our “Submit” button to the controller. To do this we need to make two modifications to our index.html
file.
First, we need to include the scripts associated with app.js
and addCtrl.js
in our index.html
file.
Then, we need to attach our addCtrl
controller to the HTML body.
Next, we need to attach our createUser()
function through the “Submit” button’s ng-click
event.
Great. Now it’s time to run a quick test. Fire up your server using node server.js
then navigate to localhost:3000
. At this point, try adding users via the HTML form. If everything’s been coded correctly, you should be able to see your newest user on the localhost:3000/users
page.
Eureka! Now onto the real reason you’re here…
Maps! This is where things get tricky. So follow closely.
At a high level, what we need to do next is to take the user data we’ve collected to this point…
Additionally, we’re going to need to build functionality for pop-ups and clickable map coordinates.
To handle all of this, we’re going to create a new Angular Factory. This factory will be used by our addCtrl
controller to complete all of the logic associated with map building.
Go ahead and open your public->app->gservice.js file
. Then paste the below code in place.
Let’s break down what’s going on in here.
gservice
and specified that it depends on the$http
service.googleMapService
object that will hold the refresh
function we’ll use to rebuild the map,locations
array will hold all of the converted locations in the database.selectedLat
and selectedLong
variables, which hold the specific location we’re looking at during any point in time.refresh()
function, which takes new coordinate data and uses it to refresh the map. To do this, the function performs an AJAX call to the database and pulls all of the saved records. It then takes these records and passes them to a function called convertToMapPoints
, which loops through each record – creating an array of Google formatted coordinates with pre-built pop-up messages. (Note that Google formats its coordinates [Lat, Long]
, so we needed to flip the order from how we saved things in MongoDB).refresh
function sets off the initialize()
function. This function creates a generic Google Map and places it in the index.html
file where the div id of map
exists. The initialize function, then loops through each of the locations in the locations
array and places a blue-dot marker on that location’s geographic position. These markers are each given a listener that opens their message boxes on click. Finally, the initialize()
function ends with a bouncing red marker being placed at the initial location (pre-set to the center of America as of now).refresh()
function is run immediately upon window load, allowing the map to be seen right away.If you’re still with me, then let’s incorporate our gservice
and refresh()
functions in the rest of the app. Since we’ll be using the refresh()
function whenever we add a new user, it makes sense to include it in our addCtrl.js
file.
Modify the initial call in addCtrl
so it includes both the gservice
module and factory.
Additionally, add the below line in the $http.post
function beneath the lines where the form is cleared. This will immediately refresh the map when a new user is added.
Next, add the gservice
module to our main app.js
file.
Then add the ‘gservice’ script to our index.html
file and delete the reference to our holder image, and include the CSS to set our map’s height and width.
And finally… it’s time to test. Fire up your server.js
and let’s see what localhost:3000
looks like now. If all went well, you should be seeing two blue dots correlating to the two users we added to your database.
Woohoo! We are mapping now! As a further test, go ahead and click any one of the markers you see. You should see a pop-up window with info.
Yay! Now we’re popping now as well!
We’ve done some great things here, but right now – there is no way for users to actually set their location on the map before submitting. Everyone is just stuck in the center of Kansas. So let’s create some functionality for map clicks.
First, add the following service properties in the section for initializing variables:
Next, add the below listener to the bottom of the initialize()
function, right under the section that set the initial location as a bouncing red marker.
Run your server.js
file and test it out. If everything is working, you should be able to move the red dot around the map.
This looks great, but astute readers may have noticed that the coordinates in our form never changed to reflect the dot’s movement. The coordinates always point to Kansas. This makes sense because we never created any logic linking the marker’s movement with our Angular controller. Let’s do that now.
First, let’s add the $rootScope
to the dependency list of the gservice
factory. The reason we’re including $rootScope
here is that we’ll be broadcasting the result of clicks back to our original Angular form, so we can see the coordinates clicked onto.
Then add the logic associated with the broadcasting to $rootScope
at the conclusion of the map’s click listener event we just created.
Now, return back to the addCtrl.js
file and include the $rootScope
service in our addCtrl
controller.
Finally, add the below function on top of the createUser
function. Here you can see that this function listens for when the gservice
function broadcasts the “click” event. On click, the addCtrl
controller will set the value of the latitude and longitude of the form equal to the click coordinates (rounded to 3). It will also note that the location has not been HTML Verified (to differentiate between spam and authentic locations).
Go ahead and test it now.
Looking good!
At this point, we have a pretty slick app on our hands. Up until now, we’re leaving it to users to provide us with their actual location. This might be fine if we’re okay with crappy, spam data. But if we want a way to discriminate true locations – we need something better.
This is where HTML5 Geolocation comes in. With the latest version of HTML comes the ability to identify precisely where a user is located–so long as they grant permission in the browser. Let’s add one last function to our app that sets the initial location of our user’s dot to their HTML5 verified location.
To do this, we’ll be utilizing the open-source angularjs-geolocation
library. The library makes it easy to incorporate HTML5 geolocation requests in Angular applications. Since we’ve already added references to the geolocation
service in our addCtrl
and app.js
files, all we need to do is include the logic associated with such a call in addCtrl
. Simply paste the below code in the initialization section of addCtrl.js
after the initial coordinates are set.
The logic here uses a simple geolocation.getLocation
function to return coordinate data. This coordinate data is then parsed, rounded to three decimal points (for privacy reasons), and then passed to the $scope.formData.longitude
and $scope.formData.latitude
. Once this takes place, we refresh the map and pass in the newest coordinates to be added.
Let’s test this out. If things went correctly, your browser should have asked you for location access and then moved your red dot near your precise location.
We really covered a lot today. But hopefully, this exercise has left you empowered to chart your own map-making path and to lay your mark on the world at large. (Puns definitely intended.)
We’ll be back in a week or so for Part II, where we’ll enhance our newly created Map App with querying and filtering tools. In the meantime, keep experimenting and adding new features at your own pace.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!