Este artículo está orientado a desarrolladores principiantes de Angular que deseen integrar Leaflet en sus aplicaciones. La integración de dependencias de terceros en Angular puede ser desde trivial hasta problemática. Mientras que Leaflet tiende más hacia el lado trivial, los mapas que se ajustan de forma fluida con el tamaño del navegador pueden presentar algunos problemas de configuración en relación con el ciclo de vida de Angular.
Antes de comenzar la deconstrucción, apunta tu amigable navegador de barrio al siguiente Github.
Ahora, puedes seguir a lo largo o bifurcar el proyecto y utilizarlo como base para tus propias aplicaciones.
Contexto
Este tutorial presume una pequeña cantidad de conocimiento previo de Leaflet, pero no más de lo que se expresa en la Guía de Inicio Rápido de Leaflet.
ArcGis se utiliza para el mosaico en la aplicación cubierta en este artículo (el paquete esri-leaflet se instala desde npm).
Configuración de Angular
Muy poco hay que hacer después de la omnipresente instalación npm. Los estilos necesarios de Leaflet están disponibles a través del archivo angular.json,
"styles": ,
He tenido resultados mixtos con @types/leaflet, por lo que esta aplicación no explota las tipificaciones en aras de la simplicidad. Leaflet y azulejos ESRI se importan como se muestra a continuación,
import * as esri from 'esri-leaflet';
import * as L from 'leaflet';
Un mapa Leaflet, por ejemplo, se escribe temporalmente como cualquiera; siéntase libre de afirmar las tipificaciones como un ejercicio después de haber deconstruido el tutorial.
Las coordenadas del centro de un mapa son importantes para la inicialización del mapa. Muchas demos hardcodean esta información. Como alternativa, este tutorial ilustra cómo inyectar coordenadas del centro del mapa utilizando los InjectionToken de Angular.
Una coordenada es un punto bidimensional en un mapa, representado por latitud y longitud. Un InjectionToken para una coordenada puede ser creado como se ilustra en el archivo /src/app/tokens.ts,
import { InjectionToken } from '@angular/core';
export const INIT_COORDS = new InjectionToken<{lat: number, long: number}>('INIT_COORDS');
INIT_COORDS se utiliza en el archivo /src/app/app.module.ts para realizar la provisión (o definición) real (lat/long),
providers: ,
Esto mueve la definición del centro del mapa al nivel más alto en la aplicación Angular (donde es fácilmente visto y casi auto-documentado).
Para inyectar esta coordenada en la aplicación, importa INIT_COORDS desde el archivo tokens e inyecta desde un constructor de clase. Esto se ilustra en el archivo /src/app/map.component.ts (el componente principal del mapa en este tutorial),
constructor( @Inject(INIT_COORDS) protected _initCoords: {lat: number, long: number} )
Componente del mapa
La aplicación cubierta en este artículo muestra un mapa de altura fija con un ancho que se extiende a través de toda la ventana del navegador. El mapa se redibuja cuando la ventana cambia de tamaño. Algunos marcadores también se renderizan encima de los mosaicos del mapa.
El mapa se integra en la aplicación a través de la plantilla del componente principal de la app, como se muestra a continuación
/src/app/app.component.html
<app-map ="markers"></app-map>
y el código fuente del componente del mapa se encuentra en /src/app/maps/map.component.ts .
El mapa se renderiza en un contenedor (DIV) que se encuentra actualmente en la plantilla del componente de mapa,
/src/app/maps/map.component.html
<div>
<div>
<div #primaryMap ="currentHeight" ="currentWidth"></div>
<map-control class="map-control"></map-control>
<div class="mouse-coords">
<span></span>
<span ="mcText"></span>
</div>
</div>
</div>
Si el ancho y el alto del DIV contenedor son fijos, entonces los mosaicos del mapa pueden ser renderizados en un punto temprano del ciclo de vida del componente. Hay dos enfoques posibles para pasar la anchura y la altura variables, outside-in y inside-out. El método outside-in define las entradas de Angular para la anchura y la altura. El componente padre es responsable de comunicar la anchura y la altura al componente del mapa. El método inside-out define la anchura y la altura de forma interna al componente del mapa y «pasa» esa información a su plantilla. La mayoría de las aplicaciones utilizan el método outside-in. Este tutorial emplea el método «inside-out» con fines ilustrativos. Modifique el código para cambiar el paso de la anchura y la altura como ejercicio.
La anchura y la altura variables introducen algunas cuestiones sutiles relativas a la representación del mapa. Es tentador realizar todo lo relacionado con la configuración de Leaflet en el manejador on-init, y esto típicamente funciona con ancho y alto fijos. Dado que el ancho del mapa varía con el tamaño del navegador en este tutorial, es mejor separar el proceso de inicialización del mapa entre los manejadores del ciclo de vida on-init y after-view-init. Esto se ilustra en los siguientes segmentos de código de /src/app/maps/map.component.ts .
En primer lugar, la identificación de un DIV contenedor para la inicialización del mapa se descarga a un ViewChild,
@ViewChild('primaryMap', {static: true}) protected mapDivRef: ElementRef;
protected mapDiv: HTMLDivElement;
La mayor parte de la configuración del mapa se realiza en ngOnInit,
public ngOnInit(): void
{
// Reference to DIV containing map is used in Leaflet initialization
this.mapDiv = this.mapDivRef.nativeElement;
this.__initializeMap();
this.__renderMap();
this.__showMarkers();
}
La invalidación del tamaño del mapa se difiere a ngAfterViewInit, cuando el ancho y el alto completos necesarios para que los mosaicos del mapa funcionen están disponibles. La variable map hace referencia al mapa Leaflet creado en el manejador ngOnInit.
public ngAfterViewInit(): void
{
this.map.invalidateSize();
this.__initMapHandlers();
}
Nota que el manejador de actualización del tamaño del mapa vuelve a calcular las variables currentWidth y currentHeight de la clase, que están vinculadas a los estilos DIV que controlan el ancho y el alto del mapa DIV. Leaflet utiliza esa información de anchura/altura para reajustar y volver a renderizar el mapa.
Dado que la definición de la anchura/altura del mapa y el redimensionamiento del mapa con los cambios de la ventana se manejan internamente en el componente del mapa, se añade un simple manejador del tamaño de la ventana.
@HostListener('window:resize', )
protected __onResize(event: any): void
{
this.__updateMapSize();
this.map.invalidateSize();
}
Esto también podría ser manejado por un servicio externo, ejecutado fuera de Angular, y depurado. Si hay suficiente interés, proporcionaré un tutorial que ilustra la técnica.
Map Controls
Leaflet permite controles personalizados, pero también puedes usar componentes Angular como controles de mapa. Crea el componente Angular y utiliza CSS para el posicionamiento. He utilizado esto en varios proyectos para los controles de zoom altamente personalizados. Ver la carpeta /src/app/maps/map-control para un esqueleto que puede ser utilizado como punto de partida.
Referirse a /src/app/maps/map.component.scss para el estilo relevante.
Cuidado con el z-index
Si el mosaico o algún otro aspecto de la visualización del mapa no parece funcionar, puede ser un problema con z-index. En el pasado, he tenido que volver a mapear los índices z simplemente por cambiar los proveedores de mosaicos o incluso por migrar de Angular 7 a Angular 8!
Marcadores de mapa
Se pueden añadir marcadores con coordenadas conocidas a un mapa. Definir las coordenadas y luego crear un Icono Leaflet para cada uno. Las coordenadas de los marcadores para este tutorial se definen en el componente principal de la app,
/src/app/app.component.ts
public markers: {lat: number, long: number}; // Map markers (relevance depends on map center)
constructor()
{
// some map markers
this.markers = ;
}
y se pasan como un Input de Angular al componente del mapa,
/src/app/app.component.html
<app-map ="markers"></app-map>
Los marcadores se renderizan en el componente de mapa,
/src/app/maps/map.component.ts
protected __showMarkers(): void
{
if (this.markers !== undefined && this.markers != null && this.markers.length > 0)
{
// Add markers
const icon = L.icon({
iconUrl: MapIconOptions.mapIcon,
iconSize: MapIconOptions.iconSize,
iconAnchor: MapIconOptions.iconAnchor,
shadowUrl: MapIconOptions.mapShadowIcon,
shadowSize: MapIconOptions.shadowSize,
shadowAnchor: MapIconOptions.shadowAnchor,
});
const n: number = this.markers.length;
let i: number;
let m: L.marker;
let x: number;
let y: number;
for (i = 0; i < n; ++i) {
x = this.markers.lat;
y = this.markers.long;
if (x !== undefined && !isNaN(x) && y !== undefined && !isNaN(y))
{
// okay to add the icon
m = L.marker(, {icon: icon}).addTo(this.map);
}
else
{
// implement your own error handling
console.log('MARKER ERROR, Marker number: ', (i+1), 'x: ', x, ' y: ', y);
}
}
}
}
Interactividad
Se han añadido un par de manejadores para la interactividad básica del mapa, a saber, clic de ratón y movimiento de ratón. Este último actualiza la posición actual del mapa (lat/long) basándose en la posición del ratón.
/src/app/maps/map.component.ts
protected __onMapMouseMove(evt: any): void
{
const lat: string = evt.latlng.lat.toFixed(3);
const long: string = evt.latlng.lng.toFixed(3);
this.mcText = `Latitude: ${lat} Longitude: ${long}`;
}
La cadena que refleja la posición actual se muestra en la plantilla del componente del mapa,
/src/app/maps/map.component.html
<div class="mouse-coords">
<span></span>
<span ="mcText"></span>
</div>
El resultado
Construye la aplicación y deberías observar algo similar a esta captura de pantalla,
Mueve el ratón sobre el mapa para observar las actualizaciones de la posición del ratón en coordenadas del mapa.
Espero que esto te sirva para empezar a trabajar con Angular y Leaflet. Estas dos aplicaciones funcionan muy bien juntas y hay un potencial casi ilimitado para mapas altamente interactivos y atractivos.
¡Buena suerte con tus esfuerzos en Angular!
Para más bondades de Angular, asegúrate de revisar el último episodio del podcast The Angular Show.
ng-conf: The Musical es una conferencia de dos días de la gente de ng-conf que viene el 22 de abril & 23, 2021. Compruébalo en ng-conf.org