Friday, June 2, 2017

Angular Carto: Map Factory

This is part of a series covering how to create reusable Carto map components. Read the introduction to learn why one would even want to do such a thing.

In this post, we will discuss how to add a Carto map to a website and how to register the map with Angular so that our Angular components can manipulate the map display.

Reference the scripts

Before jumping into your new Carto map, you will first need to make sure that your web page has references to the Carto and Angular javascript libraries. In order for the map to tile correctly, you will also have to reference Carto’s standard stylesheet.

Sizing web page and containers

If you’re looking for an immersive map that takes up the entire web page, you will need to make sure that the html and body elements and the map container are all styled to fill all space available to them. My preferred method is to define a site-wide style implementing those requirements, including a carto-map-container CSS class that can be applied to the element where the Carto map is inserted.

Creating a container for the map

The map is inserted in a placeholder div element. You will need to assign that element an id. This id will later be passed to Carto to indicate where the map will be created. You will also need to assign the container div to the carto-map-container CSS class, which we defined above to expand to fill all space available. Experience has shown me that the map container div must be the first child of the body element for Carto to work correctly in Internet Explorer.

Registering a map factory with Angular

In a vanilla Carto web page, adding a map is as simple as instantiating the L.map class. (Technically, this is a Leaflet class, but the distinction is not relevant to the present post.) The complication of using Angular is that we will be building Angular components that manipulate the map, so we need a reliable way for those components to find the Leaflet/Carto map object. The method Angular uses for this is dependency injection. As we build angular components throughout this series, they will all take a parameter named map as a parameter. The Angular dependency injector will inspect each component’s constructor and provide the map object registered under the name “map” as the argument to the map parameter.

To register the map with Angular, we will register an anonymous function that instantiates and configures the map as factory under the name “map”. (The many options and functions to customize Leaflet/Carto map objects are beyond the scope of this post, so review the Leaflet documentation if you need to learn more.) Once another Angular component requests the map by having a parameter named “map” in its constructor, Angular will invoke the anonymous function to build the map, and store the resulting value to provide for all future requests of the map. In other words, the map creation function is called only once.

You should also note that there is another dependency injection at work in this code. The first argument of the L.map initializer is the element ID of the Carto map container. Rather than providing this ID as a string literal, map factory takes a parameter mapContainerID. The actual element ID is registered with the Angular dependency injector using the value. Putting the dependency injector in control of where the map is created loosens the coupling between the map factory and the HTML.

Bringing it all together

In a normal application where there’s at least one component that requires the map, all we would have to do is register the map factory. However, the example code doesn’t have any Angular components, so we need to request the map from the Angular dependency injector ourselves. You can safely ignore the window.onload handler in the sample code, because having at least one map-dependent Angular component on your page will obviate the need for it.

You can Fiddle around with a working Angular Carto map below.

Why would you do such a thing?

Why are we registering an anonymous function as an Angular factory, then jumping through hoops to get the Angular dependency injector to invoke the registered function? Why not just instantiate and configure the Leaflet map and be done with it? Good question! The example code provided in this post is definitely more complicated than necessary for the task of putting a map on a web page. Dependency injection shines when we have a complicated application with many components that manipulate the map. Over the course of this series, I think you will begin to see what I mean.

Design pattern bingo

Dependency injection

Angular provider (factory)

Other posts in this series

Revision history

2017-06-06: The example code in this page originally passed the element ID carto-map of the Carto map container as a string literal to the map initialization function L.map. That was poor Angular style and has been rectified.