The MEAN Stack and Leaflet - My Notes

By peterm, 12 January, 2015

I've been looking at how to refactor our campus maps app from being Drupal based to sometihng that is based on a RESTful API so that data can be easily shared with other apps. Drupal has gotten us a long way, but the map stack of modules we're using has some limitations. I need to pull some custom map work stored at Mapbox.com giving us deeper zooms and styling rules we've decided upon.

The MEAN stack offers one refactoring option. So, I spent Christmas vacation doing some reading, configuring and thinking about how the MEAN stack might work for our needs.

I'll leave the background reading of what the MEAN stack is and jump into where I ran into problems and how they got resovled. 

I decided to use the meanjs.org distribution since it was implmenting Yeoman style generators and I was familiar with yo and grunt from an earlier project. In that earlier project, I had done some experimenting using AngularJS and the angular-leaflet-directive package to get a Leafletjs map running within a Yeoman scaffolded app. 

Getting setup based on the docs was pretty straightforward. You need to get npm setup and download the packages per the docs. I used some of the MEAN tutorials out there to help get oriented to the stack. 

The first set of obstacles to overcome have to do with trying to do something beyond the boilerplate. Most of the tutorials I went through got you started, but didn't add any additional Angular modules or talk much about configuration. The basic steps to getting Leaflet running in the MEAN stack went like this.

Follow the Getting Started docs to install Mongo, Express, Node, Angular.

Create a working directory

mkdir meanjs_leaflet

Create a MEAN app

yo meanjs

Add a Package with Bower

bower install angular-leaflet-directive --save

Configure the App and Package

  • edit config/env/all.js we need to add leaflet.css to css section
  • edit config/env/all.js and add leaflet.js and angular-leaflet-directive.min.js to js section
  • edit public/modules/config.js to add 'leaflet-directive'

Edit the View

  • edit public/modules/leaflet/views/leaflet.client.view.html. Note that we needed to give width and height to the directive before it would render properly:

<leaflet center="london" markers="markers" url-hash-center="yes" width="640" height="400"></leaflet>

Edit the Controller

  • edit public/modules/leaflet/controllers/leaflet.client.controller.js to center the map on london with a zoom of 10

// Controller Logic

          // ...

          angular.extend($scope, {

               london: {

                    lat: 51.505,

                    lng: -0.09,

                    zoom: 10

                },

                markers: {

                    main_marker: {

                        lat: 51.5,

                        lng: -0.09,

                        focus: true,

                        //message: 'Hey, drag me if you want',

                        title: 'Marker',

                        draggable: true,

                        label: {

                            message: 'Hey, drag london if you want',

                            options: {

                                noHide: true

                            }

                        }

                    }

                }

          });

  • Edit the leaflet directive in the view to include the "markers" element so that they'll display

<leaflet center="london" markers="markers" height="480px" width="640px">

Set and Get Data via Mongo

When you run the initial command yo <app name> you're run through a series of questions. One question is whether you want to have the Articles module. This contains CRUD examples that can be used to inspect how things work. Alternatively, you can run CRUD generator commands. I did both in my experimenting. I gave my module the name of leaflet and the generator used that as a default for creating CRUD files.

The MEAN stack creates a controller with CRUD based actions. My basic test was to be able to get a form with name and coordinates fields and insert data to Mongo. In order to do that, I had to edit the app/models/leaflet.server.model.js file. I simple added a coordinates: [], entry below the created block.

I then edited the public/modules/leaflets/views/create-leaflet.client.view.html file and copy/pasted/renamed the form fields to coordinates.

<label class="control-label" for="coordinates">Coordinates</label>

<div class="controls">

  <input type="text" data-ng-model="coordinates" id="coordinates" class="form-control" placeholder="coordinates" required>

</div>

This should allow us to gather data via the form and insert into Mongo. At this point you should get a copy of Robomongo which will help while you memorize new noSQL commands to get data in and out of Mongo.

Now that you've got name and locative information being stored, it is time to move on to getting that data and adding a marker to your map.

The Controller

Your app should have a controller located at public/modules/<module name>/controllers/<your controller file>. What I did was work in the findOne function so that when finding a specific leaflet item, we'd be able to:

  • center on those coordinates
  • zoom to a specified level
  • add a marker with the name of the location

So, here's where I lost hours that you might save. First, I had to learn about $promise. Sure, I'd read about them, but didn't know how to work with them. Basically, we needed to add the following below the findOne() function to allow the returned results to be manipulated. I ended up with this which may be improved upon.

// Find existing Leaflet

$scope.findOne = function() {

$scope.leaflet = Leaflets.get({ 

leafletId: $stateParams.leafletId

});

// process the coordinates coming from findOne()

$scope.leaflet.$promise.then(function(data) {

var toProcess = data.coordinates[0];

var values = toProcess.split(",");

var  ucsclat;

var  ucsclng;

ucsclat = parseFloat(values[0]);

ucsclng = parseFloat(values[1]);

$scope.leaflet.lat = ucsclat;

$scope.leaflet.lng = ucsclng;

$scope.leaflet.name = data.name;

$scope.leaflet.zoom = 14;

angular.extend($scope, {

               center: {

                    lat: $scope.leaflet.lat,

                    lng: $scope.leaflet.lng,

                    zoom: $scope.leaflet.zoom

                },

                markers: {

                mainMarker: {

                lat: $scope.leaflet.lat,

                lng: $scope.leaflet.lng,

                focus: true,

                message: $scope.leaflet.name,

                draggable: true

                }

                }

          }); // end of angular.extend

}); // end of $promise.then()

}; // end of findOne()

While this was largely working, I continued to get an error telling me that center was not defined in main scope. That took hours more to research. Once I plugged this into the top of the controller file, the error went away.

$scope.center = {}; // would not zoom till this was defined

Reading further, I've come to find that we might not want to use angular.extend to manipulate the scope, but rather to simply set the values. More on that as I continue my studies.