UNPKG

@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
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