Merida Design Blog

Publicado el | Tutoriales / , ,

Como crear una directiva de Angular para Google Maps

Demo del Artículo | Ejemplo en Codepen.io

Las directivas son una de las características que mas me gustan Angular, nos ayudan a extender la funcionalidad de nuestras apps tanto como queramos, ya sea integrando aplicaciones de terceros o simplemente para encapsular y reutilizar algunas partes de nuestro código.

En este tutorial vamos a ver como crear una directiva para usar el API de Google Maps, el cual ofrece muchas posibilidades de configuración, las cuales puedes consultar en su página, pero para fines de este tutorial veremos solamente las siguientes:

  • Pintar y centrar el mapa en la ubicación indicada
  • Configurar controles de mapa:
    • Pan
    • Street Map
    • Zoom
  • Markers

NOTA: El estilo de la estructura del código que usaré está basado en la guía de estilo para Angular de John Papa, te recomiendo leerla y aplicarla a tus proyectos ;D.


Archivos

Antes de comenzar, crea una carpeta para el proyecto y dentro de ésta crea la siguiente estructura:


carpeta_proyecto/
---- css/ 
-------- styles.css
---- js/
-------- app.js
-------- controller.js
---- libs/
-------- md-google-maps.js
index.html


EL primer archivo que vamos a modificar es index.html, en él colocaremos la estructura de la aplicación e incluiremos todos los archivos.

HTML

	
	<!DOCTYPE html>
	<html>
	
	  <head>
	    <title>Directiva de Google Maps</title>
	    <link rel="stylesheet" href="css/styles.css">
	    <script src="http://maps.google.com/maps/api/js?v=3.exp"></script>
	  </head>
	
	  <body ng-app="mapApp" ng-controller="mapCtrl as Map">
	    
	    <div class="center">
	      <h1>Directiva de Google Maps</h1>
	      <md-google-maps></md-google-maps>
	      <div class="buttons-bar">
	        <a ng-click="Map.control.addMarker()" class="button-add">Agregar Marker</a>
	        <a ng-click="Map.control.clearMarkers()" class="button-del">Eliminar Markers</a>
	      </div>
	    </div>
	    
	    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
	    <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
	    <script src="libs/md-google-maps.js"></script>
	    <script src="js/app.js"></script>
	  </body>
	
	</html>
	

Por ahora solo incluimos la directiva md-google-maps y un par de botones que nos servirán para crear y eliminar los Markers.

Como puedes ver en la línea 23 también he incluido la librería lodash.js, ésta es una versión más optimizada de la librería underscore.js que nos ayuda con la manipulación de arreglos y colecciones de objetos, entre otras funciones.


App Script (Modulo y Controlador)

A continuación definimos nuestro módulo mapApp y el controlador mapCtrl los cuales hemos declarado en la línea 10 del index.html.


	(function () {
	  'use strict';
	  
	  angular.module('mapApp', ['MdGoogleMaps'])
	    .controller('mapCtrl', Controller);
	    
	  Controller.$inject = [];
	  
	  function Controller() {
	    var vm = this;
	  }
	} ());

Como puedes ver en el módulo mapApp hemos definido la dependencia al módulo MdGoogleMaps el cual carga nuestra directiva, la cual haremos a continuación.


Directiva (estructura)

Por ahora solo vamos a definir la directiva y remplazarla con un div que contiene un identificador al cual podamos acceder para asignarle estilos, el resto de la funcionalidad la agregaremos en un momento.


	(function () {
	  'use strict';
	  
	  angular.module('MdGoogleMaps', [])
	    .directive('mdGoogleMaps', MdGoogleMaps);
	    
	  MdGoogleMaps.$inject = [];
	  
	  function MdGoogleMaps() {
	    return {
	      restrict: 'E',
	      replace: true,
	      scope: {},
	      template: '<div id="md-google-maps"></div>',
	      link: function(scope, element, attrs) {
	        
	      }
	    }
	  }
	} ());

Los puntos a notar en éste código son:

restrict: 'E'
Aquí definimos el tipo de directiva que estamos creando, en este caso es un elemento, otras opciones puden ser A para incluir la directiva como un atributo ó C para incluirla como una clase.
replace: true
Indicamos que el elemento que hemos colocado en el html será reemplazado por completo con el html que definamos en la propiedad template.
_NOTA: Todos los atributos que coloquemos en nuestro elemento, serán colocados en el html de nuestro template al momento de ser reemplazado._
scope: {}
Definimos un contexto de ejecución ($scope) independiente para nuestra directiva, eliminando de esta forma cualquier dependencia con el controlador y poder colocar la directiva en cualquier vista sin que esto afecte su funcionamiento.
Todas las variables que definirán el comportamiento de la directiva serán pasadas como atributos del elemento.
template: '<div id="md-google-maps">'
El html por el que será reemplazado nuestro elemento.
link: function(...){ ...
Aquí es donde la magia sucede, ésta función se ejecuta cuando la directiva es instanciada y aquí es donde ejecutaremos todas las llamadas a la api de google para pintar el mapa.

CSS

Copia el siguiente código en tu archivo css/styles.css:


	* {
	  -webkit-box-sizing: border-box;
	     -moz-box-sizing: border-box;
	          box-sizing: border-box;
	}
	
	body {
	  background-color: #222;
	  font-family: sans-serif;
	}
	
	.buttons-bar {
	  display: -webkit-flex;
	  display: flex;
	}
	
	[class*="button-"] {
	  border: 1px solid;
	  color: #fff;
	  display: block;
	  -webkit-flex-grow: 1;
	          flex-grow: 1;
	  padding: .7em;
	  text-align: center;
	}
	
	.button-add {
	  background-color: #56AD56;
	  border-color: #3b873b;
	}
	
	.button-del {
	  background-color: #CD433D;
	  border-color: darkred;
	}
	
	.center {
	  left: 50%;
	  max-width: 100%;
	  position: absolute;
	  top: 50%;
	  -webkit-transform: translate(-50%, -50%);
	     -moz-transform: translate(-50%, -50%);
	          transform: translate(-50%, -50%);
	  width: 600px;
	}
	
	#md-google-maps {
	  background: white;
	  border: 1px solid #bbb;
	  height: 300px;
	  margin: 10px 0;
	}
	
	h1 {
	  color: white;
	  font-weight: 400;
	  margin: 0;
	  text-align: center;
	}

Una vez hecho abre el index.html en tu explorador y debe verse de la siguiente forma:

step-1


Pintar el Mapa

Para poder pintar el mapa necesitamos indicarle a google la ubicación, para ello colocamos los atributos lat y lng en la directiva.

Modifica tu index.html con el siguiente código:


	...
	<md-google-maps lat="37.6443386" lng="-122.3793363" zoom="12"></md-google-maps>
	...
	

Ahora usemos esos valores dentro de la directiva para crear el mapa e insertarlo en el elemento.

    ...
    scope: {
      lat: '=',
      lng: '=',
      zoom: '='
    },
    template: '<div id="md-google-maps"></div>',
    function(scope, element, attrs) {
      var center = new google.maps.LatLng(scope.lat, scope.lng);
      var zoom = scope.zoom || 12;
      var mapSettings = {
        center: center,
        zoom: zoom
      };
      var map = {};
	        
      initialize();
    
      function initialize() {
        map = new google.maps.Map(element[0], mapSettings);
      }
    }
  • El primer paso es definir los atributos de la directiva como variables del scope como podemos ver en la línea 14.
    Si el nombre de la variable que usaras en tu
    scope tiene un nombre distinto al del atributo entonces tu asignación, entonces debes especificar el nombre del atributo en la asignación, por ejemplo, si en lugar de lat decidieras llamarle a tu variable latitude, entonces la asignación quedaría de la siguiente forma:

	scope: {
		latitude: '=lat',
		...

  • Ahora en la función link hacemos lo siguiente:
    1. Creamos el objeto de ubicación (línea 20).
    2. Definimos un valor para el zoom (línea 21).
    3. Agrupamos los dos valores anteriores anteriores en un objeto (línea 22).
    4. Creamos el mapa con las configuraciones anteriormente definidas (línea 31).

Lee de nuevo la aplicación en tu explorador. Ahora debe verse algo como esto:

step-2

Genial no?, pero que es un mapa de google si no podemos colocar algunos Markers (indicadores de posición), así que ahora agregaremos esa funcionalidad a la directiva.


Creamos un objeto en el controlador y lo pasamos a la directiva para que le asigne las funciones que nos van a permitir controlar el mapa fuera de la directiva.

En el controlador agrega el siguiente código:

	...
	vm.control = {};

Ahora le asignamos el objeto como un nuevo atributo de la directiva:

	...
	<md-google-maps lat="37.6443386" lng="-122.3793363" zoom="12" control="Map.control"></md-google-maps>
	...

Recuerda que al asignar la opción as en la directiva ngController, le asignamos un alias al controlador(Map) el cual usamos para acceder a sus propiedades y métodos.

Ahora modifica la directiva:


	...
	scope: {
    	  lat: '=',
          lng: '=',
          zoom: '=',
          control: '='
    ...
    // Esta variable guarda los markers que se agregan al mapa para luego eliminarlos
    var arrMarkers = [];
	
    initialize();
        
    function addMarker() {
      arrMarkers.push(new google.maps.Marker({
        position: map.getCenter(),
        map: map
      }));
    }
    
    function clearMarkers() {
      if (arrMarkers.length === 0) return;
      angular.forEach(arrMarkers, function(marker) {
        marker.setMap(null);
      });
	  
      arrMarkers = [];
    }

    function initialize() {    
      map = new google.maps.Map(element[0], mapSettings);
          
      if(undefined !== scope.control) {
        scope.control.addMarker = addMarker;
        scope.control.clearMarkers = clearMarkers;
      }
    }

Ahora agregamos la llamada a esas funciones desde los botones:


    ...
    <a ng-click="Map.control.addMarker()" class="button-add">Agregar Marker</a>
    <a ng-click="Map.control.clearMarkers()" class="button-del">Eliminar Markers</a>

Listo!, ahora puedes hacer click en el botón de agregar, mover el mapa, click de nuevo y ver como los markers se van agregando dinámicamente. Haz click en el botón de eliminar y todos desaparecerán.


Opciones adicionales

Como podemos ver es sencillo usar el api de google maps, si leemos su documentación podemos ver que opciones podemos incluir en el mapa y colocar todas esas opciones adicionales en un objeto para asignarlas a la directiva como atributo e incluirlas en el mapa.

Vamos a hacer justo eso, las opciones de configuración que agregaremos son:

  • mapTypeControl: define si el control de tipos de mapa será incluido.
  • mapTypeId: el tipo de mapa que se mostrará por defecto.
  • panControl: el control que te permite moverte hacia los 4 puntos cardinales.
  • streetViewControl: define si el control para entrar en modo Vista de calle estará disponible.
  • zoomControl: define si el control del zoom estará disponible.

Modifica el controlador para incluir estas opciones en una variable llamada options:


	...
	vm.options = {
		mapTypeControl: true,
		mapTypeId: google.maps.MapTypeId.ROADMAP,
		panControl: true,
		streetViewControl: true,
		zoomControl: true
	}

Asigna el valor a la directiva:


	...
	<md-google-maps ... options="Map.options"></md-google-maps>

Ahora en la directiva incluimos los valores asignadas al atributo en el scope y posteriormente unimos su valor a la variable de configuración mapSettings de la directiva.


	...
	options: '='
	...
	function initialize() {
    	if(undefined !== scope.options)
        	mapSettings = _.merge(mapSettings, scope.options);
	...
	

Listo, el mapa automáticamente incluirá las opciones.


Conclusión

La directivas son geniales, tener la posibilidad de encapsular tanta funcionalidad en un elemento y poder reusarlo en todo el proyecto nos ayuda a agilizar el desarrollo y sobre todo, compartir esta funcionalidad con compañeros o incluirla en otros proyectos.

Espero que este artículo haya sido de utilidad, si tienes alguna duda en particular respecto a las directivas no dudes de dejar tus comentarios.



Publicaciones que pueden interesarte

    Deja un comentario

      tope
    Derechos Reservados, Merida Design 2017
    %d bloggers like this: