@ng-maps/core
Version:
**@ng-maps/core** is a simple, modular and tree-shakable library for displaying google-maps inside an angular application
1,240 lines (1,229 loc) • 99.7 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Component, Input, Output, ViewChild, QueryList, forwardRef, ContentChildren, Directive, Self, Inject, InjectionToken, NgModule } from '@angular/core';
import { BehaviorSubject, from, timer, Subscription, ReplaySubject } from 'rxjs';
import { mergeMap, sample, switchMap, map, shareReplay, distinctUntilChanged } from 'rxjs/operators';
import { __decorate, __param, __metadata } from 'tslib';
import { DOCUMENT } from '@angular/common';
class MapsAPILoader {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MapsAPILoader, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MapsAPILoader }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MapsAPILoader, decorators: [{
type: Injectable
}] });
/**
* Class to implement when you what to be able to make it work with the auto fit bounds feature
* of AGM.
*/
class FitBoundsAccessor {
}
/**
* The FitBoundsService is responsible for computing the bounds of the a single map.
*/
class FitBoundsService {
constructor(loader) {
this._boundsChangeSampleTime$ = new BehaviorSubject(200);
this._includeInBounds$ = new BehaviorSubject(new Map());
this.bounds$ = from(loader.load()).pipe(mergeMap(() => this._includeInBounds$), sample(this._boundsChangeSampleTime$.pipe(switchMap((time) => timer(0, time)))), map((includeInBounds) => this.generateBounds(includeInBounds)), shareReplay(1));
}
addToBounds(latLng) {
const id = this._createIdentifier(latLng);
if (this._includeInBounds$.value.has(id)) {
return;
}
const bounds = this._includeInBounds$.value;
bounds.set(id, latLng);
this._includeInBounds$.next(bounds);
}
removeFromBounds(latLng) {
const bounds = this._includeInBounds$.value;
bounds.delete(this._createIdentifier(latLng));
this._includeInBounds$.next(bounds);
}
changeFitBoundsChangeSampleTime(timeMs) {
this._boundsChangeSampleTime$.next(timeMs);
}
getBounds$() {
return this.bounds$;
}
_createIdentifier(latLng) {
return `${latLng.lat}+${latLng.lng}`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: FitBoundsService, deps: [{ token: MapsAPILoader }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: FitBoundsService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: FitBoundsService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: MapsAPILoader }] });
class MapsApiWrapper {
constructor(_loader, _zone) {
this._loader = _loader;
this._zone = _zone;
this._api = new Promise((resolve) => {
this._mapResolver = resolve;
});
}
async getNativeMap() {
return this._api;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MapsApiWrapper, deps: [{ token: MapsAPILoader }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MapsApiWrapper }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MapsApiWrapper, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: MapsAPILoader }, { type: i0.NgZone }] });
/**
* NgMapsViewComponent renders a Google Map.
* **Important note**: To be able see a map in the browser, you have to define a height for the
* element `map-view`.
*
* @example
* <map-view [latitude]="lat" [longitude]="lng" [zoom]="zoom"></map-view>
*/
class NgMapsViewComponent {
constructor(_mapsWrapper, _fitBoundsService, _zone) {
this._mapsWrapper = _mapsWrapper;
this._fitBoundsService = _fitBoundsService;
this._zone = _zone;
/**
* The longitude that defines the center of the map.
*/
this.longitude = 0;
/**
* The latitude that defines the center of the map.
*/
this.latitude = 0;
/**
* The zoom level of the map. The default zoom level is 8.
*/
this.zoom = 8;
/**
* Enables/disables if map is draggable.
*/
this.draggable = true;
/**
* Enables/disables zoom and center on double click. Enabled by default.
*/
this.disableDoubleClickZoom = false;
/**
* Enables/disables all default UI of the Google map. Please note: When the map is created, this
* value cannot get updated.
*/
this.disableDefaultUI = false;
/**
* If false, disables scrollwheel zooming on the map. The scrollwheel is enabled by default.
*/
this.scrollwheel = true;
/**
* If false, prevents the map from being controlled by the keyboard. Keyboard shortcuts are
* enabled by default.
*/
this.keyboardShortcuts = true;
/**
* The enabled/disabled state of the Zoom control.
*/
this.zoomControl = true;
/**
* Styles to apply to each of the default map types. Note that for Satellite/Hybrid and Terrain
* modes, these styles will only apply to labels and geometry.
*/
this.styles = [];
/**
* When true and the latitude and/or longitude values changes, the Google Maps panTo method is
* used to
* center the map. See: https://developers.google.com/maps/documentation/javascript/reference#Map
*/
this.usePanning = false;
/**
* Sets the viewport to contain the given bounds.
* If this option to `true`, the bounds get automatically computed from all elements that use the {@link NgMapsFitBounds} directive.
*/
this.fitBounds = false;
/**
* The initial enabled/disabled state of the Scale control. This is disabled by default.
*/
this.scaleControl = true;
/**
* The initial enabled/disabled state of the Map type control.
*/
this.mapTypeControl = true;
/**
* The initial enabled/disabled state of the Pan control.
*/
this.panControl = false;
/**
* The initial enabled/disabled state of the Rotate control.
*/
this.rotateControl = false;
/**
* The initial enabled/disabled state of the Fullscreen control.
*/
this.fullscreenControl = false;
/**
* The map mapTypeId. Defaults to 'roadmap'.
*/
this.mapTypeId = 'roadmap';
this._layerInstance = new Map();
/**
* When false, map icons are not clickable. A map icon represents a point of interest,
* also known as a POI. By default map icons are clickable.
*/
this.clickableIcons = true;
/**
* This setting controls how gestures on the map are handled.
* Allowed values:
* - 'cooperative' (Two-finger touch gestures pan and zoom the map. One-finger touch gestures are not handled by the map.)
* - 'greedy' (All touch gestures pan or zoom the map.)
* - 'none' (The map cannot be panned or zoomed by user gestures.)
* - 'auto' [default] (Gesture handling is either cooperative or greedy, depending on whether the page is scrollable or not.
*/
this.gestureHandling = 'auto';
/**
* Controls the automatic switching behavior for the angle of incidence of
* the map. The only allowed values are 0 and 45. The value 0 causes the map
* to always use a 0° overhead view regardless of the zoom level and
* viewport. The value 45 causes the tilt angle to automatically switch to
* 45 whenever 45° imagery is available for the current zoom level and
* viewport, and switch back to 0 whenever 45° imagery is not available
* (this is the default behavior). 45° imagery is only available for
* satellite and hybrid map types, within some locations, and at some zoom
* levels. Note: getTilt returns the current tilt angle, not the value
* specified by this option. Because getTilt and this option refer to
* different things, do not bind() the tilt property; doing so may yield
* unpredictable effects. (Default of AGM is 0 (disabled). Enable it with value 45.)
*/
this.tilt = 0;
this.subscription = new Subscription();
/**
* This event emitter gets emitted when the user clicks on the map (but not when they click on a
* marker or infoWindow).
*/
this.mapClick = new EventEmitter();
/**
* This event emitter gets emitted when the user right-clicks on the map (but not when they click
* on a marker or infoWindow).
*/
this.mapRightClick = new EventEmitter();
/**
* This event emitter gets emitted when the user double-clicks on the map (but not when they click
* on a marker or infoWindow).
*/
this.mapDblClick = new EventEmitter();
/**
* This event emitter is fired when the map center changes.
*/
this.centerChange = new EventEmitter();
/**
* This event is fired when the viewport bounds have changed.
*/
this.boundsChange = new EventEmitter();
/**
* This event is fired when the mapTypeId property changes.
*/
this.mapTypeIdChange = new EventEmitter();
/**
* This event is fired when the map becomes idle after panning or zooming.
*/
this.idle = new EventEmitter();
/**
* This event is fired when the zoom level has changed.
*/
this.zoomChange = new EventEmitter();
/**
* This event is fired when the google map is fully initialized.
* You get the google.maps.Map instance as a result of this EventEmitter.
*/
this.mapReady = new EventEmitter();
/**
* This event is fired when the visible tiles have finished loading.
*/
this.tilesLoaded = new EventEmitter();
}
/**
* Map option attributes that can change over time
*/
static { this._mapOptionsAttributes = [
'disableDoubleClickZoom',
'scrollwheel',
'draggable',
'draggableCursor',
'draggingCursor',
'keyboardShortcuts',
'zoomControl',
'zoomControlOptions',
'styles',
'streetViewControl',
'streetViewControlOptions',
'zoom',
'mapTypeControl',
'mapTypeControlOptions',
'minZoom',
'maxZoom',
'panControl',
'panControlOptions',
'rotateControl',
'rotateControlOptions',
'fullscreenControl',
'fullscreenControlOptions',
'scaleControl',
'scaleControlOptions',
'mapTypeId',
'clickableIcons',
'gestureHandling',
'tilt',
]; }
/** @internal */
ngOnInit() {
this._initMapInstance(this.container?.nativeElement);
}
async _initMapInstance(el) {
await this._mapsWrapper.createMap(el, { lat: this.latitude || 0, lng: this.longitude || 0 }, {
zoom: this.zoom,
minZoom: this.minZoom,
maxZoom: this.maxZoom,
disableDefaultUI: this.disableDefaultUI,
disableDoubleClickZoom: this.disableDoubleClickZoom,
scrollwheel: this.scrollwheel,
backgroundColor: this.backgroundColor,
draggable: this.draggable,
draggableCursor: this.draggableCursor,
draggingCursor: this.draggingCursor,
keyboardShortcuts: this.keyboardShortcuts,
styles: this.styles,
zoomControl: this.zoomControl,
zoomControlOptions: this.zoomControlOptions,
streetViewControl: this.streetViewControl,
streetViewControlOptions: this.streetViewControlOptions,
scaleControl: this.scaleControl,
scaleControlOptions: this.scaleControlOptions,
mapTypeControl: this.mapTypeControl,
mapTypeControlOptions: this.mapTypeControlOptions,
panControl: this.panControl,
panControlOptions: this.panControlOptions,
rotateControl: this.rotateControl,
rotateControlOptions: this.rotateControlOptions,
fullscreenControl: this.fullscreenControl,
fullscreenControlOptions: this.fullscreenControlOptions,
mapTypeId: this.mapTypeId,
clickableIcons: this.clickableIcons,
gestureHandling: this.gestureHandling,
tilt: this.tilt,
});
const map = await this._mapsWrapper.getNativeMap();
this.mapReady.emit(map);
// register event listeners
this._handleMapCenterChange();
this._handleMapZoomChange();
this._handleMapMouseEvents();
this._handleBoundsChange();
this._handleMapTypeIdChange();
this._handleTilesLoadedEvent();
this._handleIdleEvent();
}
_handleIdleEvent() {
throw new Error('Method not implemented.');
}
_handleTilesLoadedEvent() {
throw new Error('Method not implemented.');
}
_handleMapTypeIdChange() {
throw new Error('Method not implemented.');
}
_handleBoundsChange() {
throw new Error('Method not implemented.');
}
_handleMapMouseEvents() {
throw new Error('Method not implemented.');
}
_handleMapZoomChange() {
throw new Error('Method not implemented.');
}
_handleMapCenterChange() {
throw new Error('Method not implemented.');
}
/** @internal */
ngOnDestroy() {
// unsubscribe all registered observable subscriptions
this.subscription.unsubscribe();
// remove all listeners from the map instance
this._mapsWrapper.clearInstanceListeners();
if (this._fitBoundsSubscription) {
this._fitBoundsSubscription.unsubscribe();
}
}
/* @internal */
ngOnChanges(changes) {
this._updateMapOptionsChanges(changes);
this._updatePosition(changes);
this._layerChanges(changes);
}
_updateMapOptionsChanges(changes) {
const options = {};
const optionKeys = Object.keys(changes).filter((k) => NgMapsViewComponent._mapOptionsAttributes.includes(k));
optionKeys.forEach((k) => {
options[k] = changes[k].currentValue;
});
return this._mapsWrapper.setMapOptions(options);
}
/**
* @todo google specific
* @param changes
* @protected
*/
async _layerChanges(changes) {
if (changes.layers) {
const map = await this._mapsWrapper.getNativeMap();
const layers = Array.isArray(this.layers) ? this.layers : [this.layers];
layers.forEach((layer) => {
if (layer && !this._layerInstance.has(layer)) {
const i = new google.maps[layer]();
// @todo typings
i.setMap(map);
this._layerInstance.set(layer, i);
}
});
Array.from(this._layerInstance.keys()).forEach((layer) => {
if (!layers.includes(layer)) {
const i = this._layerInstance.get(layer);
i?.setMap(null);
this._layerInstance.delete(layer);
}
});
}
}
/**
* Triggers a resize event on the google map instance.
* When recenter is true, the of the google map gets called with the current lat/lng values or fitBounds value to recenter the map.
* Returns a promise that gets resolved after the event was triggered.
*/
triggerResize(recenter = true) {
// Note: When we would trigger the resize event and show the map in the same turn (which is a
// common case for triggering a resize event), then the resize event would not
// work (to show the map), so we trigger the event in a timeout.
return new Promise((resolve) => {
setTimeout(async () => {
await this._mapsWrapper.triggerMapEvent('resize');
if (recenter) {
this.fitBounds != null ? this._fitBounds() : this._setCenter();
}
resolve();
});
});
}
async _updatePosition(changes) {
if (changes.latitude == null &&
changes.longitude == null &&
!changes.fitBounds) {
// no position update needed
return;
}
// we prefer fitBounds in changes
if ('fitBounds' in changes) {
await this._fitBounds();
return;
}
if (typeof this.latitude === 'string') {
this.latitude = parseFloat(this.latitude);
}
if (typeof this.longitude === 'string') {
this.longitude = parseFloat(this.longitude);
}
const center = await this._mapsWrapper.getCenter();
if (!(typeof this.latitude !== 'number' || typeof this.longitude !== 'number') &&
this.latitude !== center?.lat &&
this.longitude !== center?.lng) {
await this._setCenter();
return;
}
}
_setCenter() {
const newCenter = {
lat: this.latitude,
lng: this.longitude,
};
if (this.usePanning) {
return this._mapsWrapper.panTo(newCenter);
}
else {
return this._mapsWrapper.setCenter(newCenter);
}
}
async _fitBounds() {
switch (this.fitBounds) {
case true:
this._subscribeToFitBoundsUpdates();
break;
case false:
if (this._fitBoundsSubscription) {
this._fitBoundsSubscription.unsubscribe();
}
break;
default:
if (this._fitBoundsSubscription) {
this._fitBoundsSubscription.unsubscribe();
}
return this._updateBounds(this.fitBounds);
}
}
_subscribeToFitBoundsUpdates() {
this._zone.runOutsideAngular(() => {
this._fitBoundsSubscription = this._fitBoundsService
.getBounds$()
.subscribe((b) => this._zone.run(() => this._updateBounds(b)));
});
}
async _updateBounds(bounds) {
if (bounds != null) {
/**
* await map to not update bounds till map is initialized
*/
await this._mapsWrapper.getNativeMap();
if (this.usePanning) {
return this._mapsWrapper.panToBounds(bounds, this.boundsPadding);
}
else {
return this._mapsWrapper.fitBounds(bounds, this.boundsPadding);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsViewComponent, deps: [{ token: MapsApiWrapper }, { token: FitBoundsService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.8", type: NgMapsViewComponent, selector: "ng-component", inputs: { longitude: "longitude", latitude: "latitude", zoom: "zoom", minZoom: "minZoom", maxZoom: "maxZoom", draggable: "draggable", disableDoubleClickZoom: "disableDoubleClickZoom", disableDefaultUI: "disableDefaultUI", scrollwheel: "scrollwheel", backgroundColor: "backgroundColor", draggableCursor: "draggableCursor", draggingCursor: "draggingCursor", keyboardShortcuts: "keyboardShortcuts", zoomControl: "zoomControl", zoomControlOptions: "zoomControlOptions", styles: "styles", usePanning: "usePanning", streetViewControl: "streetViewControl", streetViewControlOptions: "streetViewControlOptions", fitBounds: "fitBounds", boundsPadding: "boundsPadding", scaleControl: "scaleControl", scaleControlOptions: "scaleControlOptions", mapTypeControl: "mapTypeControl", mapTypeControlOptions: "mapTypeControlOptions", panControl: "panControl", panControlOptions: "panControlOptions", rotateControl: "rotateControl", rotateControlOptions: "rotateControlOptions", fullscreenControl: "fullscreenControl", fullscreenControlOptions: "fullscreenControlOptions", mapTypeId: "mapTypeId", layers: "layers", clickableIcons: "clickableIcons", gestureHandling: "gestureHandling", tilt: "tilt" }, outputs: { mapClick: "mapClick", mapRightClick: "mapRightClick", mapDblClick: "mapDblClick", centerChange: "centerChange", boundsChange: "boundsChange", mapTypeIdChange: "mapTypeIdChange", idle: "idle", zoomChange: "zoomChange", mapReady: "mapReady", tilesLoaded: "tilesLoaded" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
<div class="map-container-inner" #container></div>
<div class="map-content">
<ng-content></ng-content>
</div>
`, isInline: true, styles: [".map-container-inner{width:inherit;height:inherit}.map-content{display:none}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsViewComponent, decorators: [{
type: Component,
args: [{ template: `
<div class="map-container-inner" #container></div>
<div class="map-content">
<ng-content></ng-content>
</div>
`, styles: [".map-container-inner{width:inherit;height:inherit}.map-content{display:none}\n"] }]
}], ctorParameters: () => [{ type: MapsApiWrapper }, { type: FitBoundsService }, { type: i0.NgZone }], propDecorators: { longitude: [{
type: Input
}], latitude: [{
type: Input
}], zoom: [{
type: Input
}], minZoom: [{
type: Input
}], maxZoom: [{
type: Input
}], draggable: [{
type: Input
}], disableDoubleClickZoom: [{
type: Input
}], disableDefaultUI: [{
type: Input
}], scrollwheel: [{
type: Input
}], backgroundColor: [{
type: Input
}], draggableCursor: [{
type: Input
}], draggingCursor: [{
type: Input
}], keyboardShortcuts: [{
type: Input
}], zoomControl: [{
type: Input
}], zoomControlOptions: [{
type: Input
}], styles: [{
type: Input
}], usePanning: [{
type: Input
}], streetViewControl: [{
type: Input
}], streetViewControlOptions: [{
type: Input
}], fitBounds: [{
type: Input
}], boundsPadding: [{
type: Input
}], scaleControl: [{
type: Input
}], scaleControlOptions: [{
type: Input
}], mapTypeControl: [{
type: Input
}], mapTypeControlOptions: [{
type: Input
}], panControl: [{
type: Input
}], panControlOptions: [{
type: Input
}], rotateControl: [{
type: Input
}], rotateControlOptions: [{
type: Input
}], fullscreenControl: [{
type: Input
}], fullscreenControlOptions: [{
type: Input
}], mapTypeId: [{
type: Input
}], layers: [{
type: Input
}], clickableIcons: [{
type: Input
}], gestureHandling: [{
type: Input
}], tilt: [{
type: Input
}], mapClick: [{
type: Output
}], mapRightClick: [{
type: Output
}], mapDblClick: [{
type: Output
}], centerChange: [{
type: Output
}], boundsChange: [{
type: Output
}], mapTypeIdChange: [{
type: Output
}], idle: [{
type: Output
}], zoomChange: [{
type: Output
}], mapReady: [{
type: Output
}], tilesLoaded: [{
type: Output
}], container: [{
type: ViewChild,
args: ['container', { static: true }]
}] } });
class MarkerManager {
constructor(_mapsWrapper, _zone) {
this._mapsWrapper = _mapsWrapper;
this._zone = _zone;
this._markers = new Map();
}
async addMarker(marker) {
if (typeof marker.latitude !== 'number' ||
typeof marker.longitude !== 'number') {
return;
}
const m = await this._mapsWrapper.createMarker({ lat: marker.latitude, lng: marker.longitude },
// TODO typings
{
label: marker.label,
draggable: marker.draggable,
icon: marker.icon ?? marker.iconUrl,
opacity: marker.opacity,
optimized: marker.optimized,
visible: marker.visible,
zIndex: marker.zIndex,
title: marker.title,
clickable: marker.clickable,
animation: typeof marker.animation === 'string'
? google.maps.Animation[marker.animation]
: marker.animation,
});
this._markers.set(marker, m);
}
getNativeMarker(marker) {
return this._markers.get(marker);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MarkerManager, deps: [{ token: MapsApiWrapper }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MarkerManager }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: MarkerManager, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: MapsApiWrapper }, { type: i0.NgZone }] });
class InfoWindowManager {
constructor(_mapsWrapper, _zone, _markerManager) {
this._mapsWrapper = _mapsWrapper;
this._zone = _zone;
this._markerManager = _markerManager;
this._infoWindows = new Map();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: InfoWindowManager, deps: [{ token: MapsApiWrapper }, { token: i0.NgZone }, { token: MarkerManager }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: InfoWindowManager }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: InfoWindowManager, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: MapsApiWrapper }, { type: i0.NgZone }, { type: MarkerManager }] });
let infoWindowId = 0;
/**
* NgMapsInfoWindowComponent renders a info window inside a {@link NgMapsMarkerComponent} or standalone.
*
* ### Example
* ```typescript
* import { Component } from '@angular/core';
*
* @Component({
* selector: 'my-map-cmp',
* styles: [`
* map-view {
* height: 300px;
* }
* `],
* template: `
* <map-view [latitude]="lat" [longitude]="lng" [zoom]="zoom">
* <map-marker [latitude]="lat" [longitude]="lng" [label]="'M'">
* <map-info-window [disableAutoPan]="true">
* Hi, this is the content of the <strong>info window</strong>
* </map-info-window>
* </map-marker>
* </map-view>
* `
* })
* ```
*/
class NgMapsInfoWindowComponent {
// @todo how to add correct typings?
constructor(_infoWindowManager, elementRef) {
this._infoWindowManager = _infoWindowManager;
this.elementRef = elementRef;
/**
* Sets the open state for the InfoWindow. You can also call the open() and close() methods.
*/
this.isOpen = false;
/**
* Emits an event when the info window is closed.
*/
this.infoWindowClose = new EventEmitter();
this._infoWindowAddedToManager = false;
this._id = (infoWindowId++).toString();
}
static { this._infoWindowOptionsInputs = [
'disableAutoPan',
'maxWidth',
]; }
ngOnInit() {
this._infoWindowManager.addInfoWindow(this).then(() => {
this._infoWindowAddedToManager = true;
this._updateOpenState();
this._registerEventListeners();
});
}
/** @internal */
ngOnChanges(changes) {
if (!this._infoWindowAddedToManager) {
return;
}
if ((changes.latitude || changes.longitude) &&
typeof this.latitude === 'number' &&
typeof this.longitude === 'number') {
this._infoWindowManager.setPosition(this);
}
if (changes.zIndex) {
this._infoWindowManager.setZIndex(this);
}
if (changes.isOpen) {
this._updateOpenState();
}
this._setInfoWindowOptions(changes);
}
_registerEventListeners() {
this._infoWindowManager
.createEventObservable('closeclick', this)
.subscribe(() => {
this.isOpen = false;
this.infoWindowClose.emit();
});
}
_updateOpenState() {
this.isOpen ? this.open() : this.close();
}
_setInfoWindowOptions(changes) {
const options = {};
const optionKeys = Object.keys(changes).filter((k) => NgMapsInfoWindowComponent._infoWindowOptionsInputs.includes(k));
optionKeys.forEach((k) => {
options[k] = changes[k].currentValue;
});
this._infoWindowManager.setOptions(this, options);
}
/**
* Opens the info window.
*/
open(event) {
return this._infoWindowManager.open(this, event);
}
/**
* Closes the info window.
*/
async close() {
await this._infoWindowManager.close(this);
return this.infoWindowClose.emit();
}
/** @internal */
id() {
return this._id;
}
/** @internal */
toString() {
return `NgMapsInfoWindowComponent-${this._id.toString()}`;
}
/** @internal */
ngOnDestroy() {
this._infoWindowManager.deleteInfoWindow(this);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsInfoWindowComponent, deps: [{ token: InfoWindowManager }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.8", type: NgMapsInfoWindowComponent, selector: "map-info-window", inputs: { latitude: "latitude", longitude: "longitude", disableAutoPan: "disableAutoPan", zIndex: "zIndex", maxWidth: "maxWidth", isOpen: "isOpen" }, outputs: { infoWindowClose: "infoWindowClose" }, viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
<div class="info-window-content" #content>
<ng-content></ng-content>
</div>
`, isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsInfoWindowComponent, decorators: [{
type: Component,
args: [{
selector: 'map-info-window',
template: `
<div class="info-window-content" #content>
<ng-content></ng-content>
</div>
`,
}]
}], ctorParameters: () => [{ type: InfoWindowManager }, { type: i0.ElementRef }], propDecorators: { latitude: [{
type: Input
}], longitude: [{
type: Input
}], disableAutoPan: [{
type: Input
}], zIndex: [{
type: Input
}], maxWidth: [{
type: Input
}], content: [{
type: ViewChild,
args: ['content', { static: true }]
}], isOpen: [{
type: Input
}], infoWindowClose: [{
type: Output
}] } });
let markerId = 0;
/**
* NgMapsMarkerComponent renders a map marker inside a {@link NgMapsViewComponent}.
*
* @example
* <agm-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
* <agm-marker [latitude]="lat" [longitude]="lng" label="M"></agm-marker>
* </agm-map>
*/
class NgMapsMarkerComponent {
constructor(markerManager) {
this.markerManager = markerManager;
/**
* If true, the marker can be dragged. Default value is false.
*/
// eslint-disable-next-line @angular-eslint/no-input-rename
this.draggable = false;
this.icon = null;
/**
* If true, the marker is visible
*/
this.visible = true;
/**
* Whether to automatically open the child info window when the marker is clicked.
*/
this.openInfoWindow = true;
/**
* The marker's opacity between 0.0 and 1.0.
*/
this.opacity = 1;
/**
* Marker optimize flag. If it is false then it prevent duplicate rendering.
* Default it is true
*/
this.optimized = true;
/**
* All markers are displayed on the map in order of their zIndex, with higher values displaying in
* front of markers with lower values. By default, markers are displayed according to their
* vertical position on screen, with lower markers appearing in front of markers further up the
* screen.
*/
this.zIndex = 1;
/**
* If true, the marker can be clicked. Default value is true.
*/
// eslint-disable-next-line @angular-eslint/no-input-rename
this.clickable = true;
/**
* This event emitter gets emitted when the user clicks on the marker.
*/
this.markerClick = new EventEmitter();
/**
* This event is fired when the user rightclicks on the marker.
*/
this.markerRightClick = new EventEmitter();
/**
* This event is fired when the user starts dragging the marker.
*/
this.dragStart = new EventEmitter();
/**
* This event is repeatedly fired while the user drags the marker.
*/
this.drag = new EventEmitter();
/**
* This event is fired when the user stops dragging the marker.
*/
this.dragEnd = new EventEmitter();
/**
* This event is fired when the user mouses over the marker.
*/
this.mouseOver = new EventEmitter();
/**
* This event is fired when the user mouses outside the marker.
*/
this.mouseOut = new EventEmitter();
/**
* @internal
*/
this.infoWindow = new QueryList();
this._markerAddedToManger = false;
this.subscription = new Subscription();
this._fitBoundsDetails$ = new ReplaySubject(1);
this._id = (markerId++).toString();
}
/**
* @internal
*/
ngAfterContentInit() {
this.handleInfoWindowUpdate();
this.infoWindow.changes.subscribe(() => this.handleInfoWindowUpdate());
}
handleInfoWindowUpdate() {
if (this.infoWindow.length > 1) {
throw new Error('Expected no more than one info window.');
}
this.infoWindow.forEach((marker) => {
marker.hostMarker = this;
});
}
/**
* @internal
*/
ngOnChanges(changes) {
if (typeof this.latitude === 'string') {
this.latitude = Number(this.latitude);
}
if (typeof this.longitude === 'string') {
this.longitude = Number(this.longitude);
}
if (typeof this.latitude !== 'number' ||
typeof this.longitude !== 'number') {
return;
}
if (!this._markerAddedToManger) {
this.markerManager.addMarker(this).then(() => {
this._updateFitBoundsDetails();
this._markerAddedToManger = true;
this._addEventListeners();
});
return;
}
if (changes.latitude || changes.longitude) {
this.markerManager.updateMarkerPosition(this);
this._updateFitBoundsDetails();
}
if (changes.title) {
this.markerManager.updateTitle(this);
}
if (changes.label) {
this.markerManager.updateLabel(this);
}
if (changes.draggable) {
this.markerManager.updateDraggable(this);
}
if (changes.iconUrl) {
this.markerManager.updateIconLegacy(this);
}
if (changes.icon) {
this.markerManager.updateIcon(this);
}
if (changes.opacity) {
this.markerManager.updateOpacity(this);
}
if (changes.visible) {
this.markerManager.updateVisible(this);
}
if (changes.zIndex) {
this.markerManager.updateZIndex(this);
}
if (changes.clickable) {
this.markerManager.updateClickable(this);
}
if (changes.animation) {
this.markerManager.updateAnimation(this);
}
}
/**
* @internal
*/
getFitBoundsDetails$() {
return this._fitBoundsDetails$.asObservable();
}
_updateFitBoundsDetails() {
if (this.latitude && this.longitude) {
this._fitBoundsDetails$.next({
latLng: { lat: this.latitude, lng: this.longitude },
});
}
}
_addEventListeners() {
const cs = this.markerManager
.createEventObservable(['click', 'pointerdown'], this)
.subscribe({
next: (event) => {
if (this.openInfoWindow) {
this.infoWindow.forEach((infoWindow) => infoWindow.open(event));
}
this.markerClick.emit(this);
},
});
this.subscription.add(cs);
const rc = this.markerManager
.createEventObservable('rightclick', this)
.subscribe(() => {
this.markerRightClick.emit();
});
this.subscription.add(rc);
const ds = this.markerManager
.createEventObservable('dragstart', this)
.subscribe((e) => {
this.dragStart.emit(e);
});
this.subscription.add(ds);
const d = this.markerManager
.createEventObservable('drag', this)
.subscribe((e) => {
this.drag.emit(e);
});
this.subscription.add(d);
const dragend = this.markerManager
.createEventObservable('dragend', this)
.subscribe((e) => {
this.dragEnd.emit(e);
});
this.subscription.add(dragend);
const mouseover = this.markerManager
.createEventObservable(['mouseover', 'pointerenter'], this)
.subscribe((e) => {
this.mouseOver.emit(e);
});
this.subscription.add(mouseover);
const mouseout = this.markerManager
.createEventObservable(['mouseout', 'pointerleave'], this)
.subscribe((e) => {
this.mouseOut.emit(e);
});
this.subscription.add(mouseout);
}
/** @internal */
id() {
return this._id;
}
/** @internal */
toString() {
return `NgMapsMarker-${this._id}`;
}
/** @internal */
ngOnDestroy() {
this.markerManager.deleteMarker(this);
// unsubscribe all registered observable subscription
this.subscription.unsubscribe();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsMarkerComponent, deps: [{ token: MarkerManager }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.8", type: NgMapsMarkerComponent, selector: "map-marker", inputs: { latitude: "latitude", longitude: "longitude", title: "title", label: "label", draggable: ["markerDraggable", "draggable"], iconUrl: "iconUrl", icon: "icon", openInfoWindow: "openInfoWindow", opacity: "opacity", optimized: "optimized", visible: "visible", zIndex: "zIndex", animation: "animation", clickable: ["markerClickable", "clickable"] }, outputs: { markerClick: "markerClick", dragStart: "dragStart", drag: "drag", dragEnd: "dragEnd", mouseOver: "mouseOver", mouseOut: "mouseOut", markerRightClick: "markerRightClick" }, providers: [
{
provide: FitBoundsAccessor,
useExisting: forwardRef(() => NgMapsMarkerComponent),
},
], queries: [{ propertyName: "infoWindow", predicate: NgMapsInfoWindowComponent }], usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsMarkerComponent, decorators: [{
type: Component,
args: [{
selector: 'map-marker',
providers: [
{
provide: FitBoundsAccessor,
useExisting: forwardRef(() => NgMapsMarkerComponent),
},
],
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [
'latitude',
'longitude',
'title',
'label',
// eslint-disable-next-line @angular-eslint/no-input-rename
'draggable: markerDraggable',
'iconUrl',
'icon',
'openInfoWindow',
'opacity',
'optimized',
'visible',
'zIndex',
'animation',
],
// eslint-disable-next-line @angular-eslint/no-outputs-metadata-property
outputs: [
'markerClick',
'dragStart',
// eslint-disable-next-line @angular-eslint/no-output-native
'drag',
'dragEnd',
'mouseOver',
'mouseOut',
],
template: '<ng-content></ng-content>',
}]
}], ctorParameters: () => [{ type: MarkerManager }], propDecorators: { latitude: [{
type: Input
}], longitude: [{
type: Input
}], title: [{
type: Input
}], label: [{
type: Input
}], draggable: [{
type: Input,
args: ['markerDraggable']
}], iconUrl: [{
type: Input
}], icon: [{
type: Input
}], visible: [{
type: Input
}], openInfoWindow: [{
type: Input
}], opacity: [{
type: Input
}], optimized: [{
type: Input
}], zIndex: [{
type: Input
}], clickable: [{
type: Input,
args: ['markerClickable']
}], markerClick: [{
type: Output
}], markerRightClick: [{
type: Output
}], dragStart: [{
type: Output
}], drag: [{
type: Output
}], dragEnd: [{
type: Output
}], mouseOver: [{
type: Output
}], mouseOut: [{
type: Output
}], infoWindow: [{
type: ContentChildren,
args: [NgMapsInfoWindowComponent]
}] } });
/**
* Adds the given directive to the auto fit bounds feature when the value is true.
* To make it work with you custom AGM component, you also have to implement the {@link FitBoundsAccessor} abstract class.
*
* @example
* <map-marker [mapFitBounds]="true"></map-marker>
*/
class NgMapsFitBoundsDirective {
constructor(_fitBoundsAccessor, _fitBoundsService) {
this._fitBoundsAccessor = _fitBoundsAccessor;
this._fitBoundsService = _fitBoundsService;
/**
* If the value is true, the element gets added to the bounds of the map.
* Default: true.
*/
this.mapFitBounds = true;
this._latestFitBoundsDetails = null;
this.subscription = new Subscription();
}
/**
* @internal
*/
ngOnChanges(changes) {
this._updateBounds();
}
/**
* @internal
*/
ngOnInit() {
this.subscription.add(this._fitBoundsAccessor
.getFitBoundsDetails$()
.pipe(distinctUntilChanged((x, y) => x.latLng.lat === y.latLng.lat && x.latLng.lng === y.latLng.lng))
.subscribe({
next: (details) => this._updateBounds(details),
complete: () => this._updateBounds(),
}));
}
/**
* Either the location changed, or visible status changed.
* Possible state changes are
* invisible -> visible
* visible -> invisible
* visible -> visible (new location)
*/
_updateBounds(newFitBoundsDetails) {
// either visibility will change, or location, so remove the old one anyway
if (this._latestFitBoundsDetails) {
this._fitBoundsService.removeFromBounds(this._latestFitBoundsDetails.latLng);
// don't set latestFitBoundsDetails to null, because we can toggle visibility from
// true -> false -> true, in which case we still need old value cached here
}
if (newFitBoundsDetails) {
this._latestFitBoundsDetails = newFitBoundsDetails;
}
if (this._latestFitBoundsDetails) {
if (this.mapFitBounds === true) {
this._fitBoundsService.addToBounds(this._latestFitBoundsDetails.latLng);
}
else {
this._fitBoundsService.removeFromBounds(this._latestFitBoundsDetails.latLng);
}
}
}
/**
* @internal
*/
ngOnDestroy() {
this.subscription.unsubscribe();
if (this._latestFitBoundsDetails !== null) {
this._fitBoundsService.removeFromBounds(this._latestFitBoundsDetails.latLng);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsFitBoundsDirective, deps: [{ token: FitBoundsAccessor, self: true }, { token: FitBoundsService }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.8", type: NgMapsFitBoundsDirective, selector: "[mapFitBounds]", inputs: { mapFitBounds: "mapFitBounds" }, usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: NgMapsFitBoundsDirective, decorators: [{
type: Directive,
args: [{
selector: '[mapFitBounds]',
}]
}], ctorParameters: () => [{ type: FitBoundsAccessor, decorators: [{
type: Self
}] }, { type: FitBoundsService }], propDecorators: { mapFitBounds: [{
type: Input
}] } });
class CircleManager {
constructor(_apiWrapper, _zone) {
this._apiWrapper = _apiWrapper;
this._zone = _zone;
this._circles = new Map();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: CircleManager, deps: [{ token: MapsApiWrapper }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: CircleManager }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.8", ngImport: i0, type: CircleManager, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: MapsApiWrapper }, { type: i0.NgZone }] });
class NgMapsCircleDirective {
constructor(_manager) {
this._manager = _manager;
/**
* Indicates whether this Circle handles mo