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 lines 158 kB
{"version":3,"file":"ng-maps-core.mjs","sources":["../../../../libs/core/src/lib/services/maps-api-loader/maps-api-loader.ts","../../../../libs/core/src/lib/services/fit-bounds.ts","../../../../libs/core/src/lib/services/maps-api-wrapper.ts","../../../../libs/core/src/lib/directives/map.ts","../../../../libs/core/src/lib/services/managers/marker.manager.ts","../../../../libs/core/src/lib/services/managers/info-window.manager.ts","../../../../libs/core/src/lib/directives/info-window.ts","../../../../libs/core/src/lib/directives/marker.ts","../../../../libs/core/src/lib/directives/fit-bounds.ts","../../../../libs/core/src/lib/services/managers/circle-manager.ts","../../../../libs/core/src/lib/directives/circle.ts","../../../../libs/core/src/lib/services/managers/polygon.manager.ts","../../../../libs/core/src/lib/directives/polygon.ts","../../../../libs/core/src/lib/services/managers/polyline-manager.ts","../../../../libs/core/src/lib/directives/polyline-point.ts","../../../../libs/core/src/lib/directives/polyline.ts","../../../../libs/core/src/lib/services/managers/rectangle-manager.ts","../../../../libs/core/src/lib/directives/rectangle.ts","../../../../libs/core/src/lib/services/maps-api-loader/noop-maps-api-loader.ts","../../../../libs/core/src/lib/services/script-loader.service.ts","../../../../libs/core/src/lib/core.module.ts","../../../../libs/core/src/public-api.ts","../../../../libs/core/src/ng-maps-core.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\n\n@Injectable()\nexport abstract class MapsAPILoader {\n protected _window?: Window | null;\n protected _document?: Document;\n public abstract load(): Promise<void>;\n public abstract configure(config: any): void;\n}\n","import { Injectable } from '@angular/core';\nimport { BehaviorSubject, from, Observable, timer } from 'rxjs';\nimport {\n flatMap,\n map,\n mergeMap,\n sample,\n shareReplay,\n switchMap,\n} from 'rxjs/operators';\n\nimport { BoundsLiteral } from '../interface/bounds';\nimport { GeoPoint } from '../interface/geo-point';\n\nimport { MapsAPILoader } from './maps-api-loader/maps-api-loader';\n\nexport interface FitBoundsDetails {\n latLng: GeoPoint;\n}\n\n/**\n * @internal\n */\nexport type BoundsMap = Map<string, GeoPoint>;\n\n/**\n * Class to implement when you what to be able to make it work with the auto fit bounds feature\n * of AGM.\n */\nexport abstract class FitBoundsAccessor {\n public abstract getFitBoundsDetails$(): Observable<FitBoundsDetails>;\n}\n\n/**\n * The FitBoundsService is responsible for computing the bounds of the a single map.\n */\n@Injectable()\nexport abstract class FitBoundsService {\n protected readonly bounds$: Observable<BoundsLiteral>;\n protected readonly _boundsChangeSampleTime$ = new BehaviorSubject<number>(\n 200,\n );\n protected readonly _includeInBounds$ = new BehaviorSubject<BoundsMap>(\n new Map<string, GeoPoint>(),\n );\n\n constructor(loader: MapsAPILoader) {\n this.bounds$ = from(loader.load()).pipe(\n mergeMap(() => this._includeInBounds$),\n sample(\n this._boundsChangeSampleTime$.pipe(switchMap((time) => timer(0, time))),\n ),\n map((includeInBounds) => this.generateBounds(includeInBounds)),\n shareReplay(1),\n );\n }\n\n protected abstract generateBounds(\n includeInBounds: Map<string, GeoPoint>,\n ): BoundsLiteral;\n\n public addToBounds(latLng: GeoPoint) {\n const id = this._createIdentifier(latLng);\n if (this._includeInBounds$.value.has(id)) {\n return;\n }\n const bounds = this._includeInBounds$.value;\n bounds.set(id, latLng);\n this._includeInBounds$.next(bounds);\n }\n\n public removeFromBounds(\n latLng: google.maps.LatLng | google.maps.LatLngLiteral,\n ) {\n const bounds = this._includeInBounds$.value;\n bounds.delete(this._createIdentifier(latLng));\n this._includeInBounds$.next(bounds);\n }\n\n public changeFitBoundsChangeSampleTime(timeMs: number) {\n this._boundsChangeSampleTime$.next(timeMs);\n }\n\n public getBounds$(): Observable<BoundsLiteral> {\n return this.bounds$;\n }\n\n protected _createIdentifier(\n latLng: google.maps.LatLng | google.maps.LatLngLiteral,\n ): string {\n return `${latLng.lat}+${latLng.lng}`;\n }\n}\n","import { Injectable, NgZone } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nimport { BoundsLiteral } from '../interface/bounds';\nimport { CircleOptions } from '../interface/circle-options';\nimport { GeoPoint } from '../interface/geo-point';\nimport { MapOptions } from '../interface/map-options';\nimport { MarkerOptions } from '../interface/marker-options';\nimport { Padding } from '../interface/padding';\nimport { RectangleOptions } from '../interface/rectangle-options';\n\nimport { MapsAPILoader } from './maps-api-loader/maps-api-loader';\n\n@Injectable()\nexport abstract class MapsApiWrapper<T = any, C = any, R = any, I = any> {\n protected _api?: Promise<T>;\n protected _mapResolver?: (value: T) => void;\n\n constructor(protected _loader: MapsAPILoader, protected _zone: NgZone) {\n this._api = new Promise<T>((resolve) => {\n this._mapResolver = resolve;\n });\n }\n\n public abstract createMap(\n el: HTMLElement,\n center: GeoPoint,\n options: MapOptions,\n ): void | Promise<void>;\n\n public abstract setMapOptions(options: MapOptions): void | Promise<void>;\n\n public abstract createMarker(\n position: GeoPoint,\n options?: MarkerOptions,\n addToMap?: boolean,\n ): Promise<any>;\n\n public abstract clearInstanceListeners(): void;\n\n public async getNativeMap(): Promise<T | undefined> {\n return this._api;\n }\n\n public abstract triggerMapEvent(eventName: string): Promise<void>;\n\n public abstract getCenter(): Promise<GeoPoint | undefined>;\n\n public abstract setCenter(newCenter: GeoPoint): Promise<void>;\n\n public abstract panTo(newCenter: GeoPoint): Promise<void>;\n\n public abstract panToBounds(\n bounds: BoundsLiteral,\n boundsPadding?: number | Padding,\n ): void | Promise<void>;\n\n public abstract fitBounds(\n bounds: BoundsLiteral,\n boundsPadding?: number | Padding,\n ): void | Promise<void>;\n\n public abstract getBounds(): Promise<BoundsLiteral | undefined>;\n\n public abstract getZoom(): Promise<number | undefined>;\n\n public abstract setZoom(zoom: number): Promise<any>;\n\n public abstract getMapTypeId(): Promise<\n google.maps.MapTypeId | string | undefined\n >;\n\n public abstract subscribeToMapEvent(eventName: string): Observable<any>;\n\n public abstract createInfoWindow(\n center: GeoPoint | null,\n options: any,\n ): Promise<I>;\n\n // TODO typings\n public abstract createDrawingManager(\n param: any,\n addToMap?: boolean,\n ): Promise<any>;\n\n public abstract createCircle(\n center: GeoPoint,\n options: CircleOptions,\n ): Promise<C>;\n\n public abstract createRectangle(\n box: BoundsLiteral,\n options: RectangleOptions,\n ): Promise<R>;\n\n public abstract createPolyline(options: any): Promise<any>;\n\n public abstract createPolygon(options: any): Promise<any>;\n}\n","import {\n Component,\n ElementRef,\n EventEmitter,\n Input,\n NgZone,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n SimpleChanges,\n ViewChild,\n} from '@angular/core';\nimport { Subscription } from 'rxjs';\n\nimport { BoundsLiteral } from '../interface/bounds';\nimport { GeoPoint } from '../interface/geo-point';\nimport { LayerTypes } from '../interface/layers';\nimport { MapOptions } from '../interface/map-options';\nimport { Padding } from '../interface/padding';\nimport { FitBoundsService } from '../services/fit-bounds';\nimport { MapsApiWrapper } from '../services/maps-api-wrapper';\n\n/**\n * NgMapsViewComponent renders a Google Map.\n * **Important note**: To be able see a map in the browser, you have to define a height for the\n * element `map-view`.\n *\n * @example\n * <map-view [latitude]=\"lat\" [longitude]=\"lng\" [zoom]=\"zoom\"></map-view>\n */\n@Component({\n styles: [\n `\n .map-container-inner {\n width: inherit;\n height: inherit;\n }\n\n .map-content {\n display: none;\n }\n `,\n ],\n template: `\n <div class=\"map-container-inner\" #container></div>\n <div class=\"map-content\">\n <ng-content></ng-content>\n </div>\n `,\n standalone: false,\n})\nexport class NgMapsViewComponent<T>\n implements OnChanges, OnInit, OnDestroy, MapOptions\n{\n constructor(\n protected _mapsWrapper: MapsApiWrapper<T>,\n protected _fitBoundsService: FitBoundsService,\n protected _zone: NgZone,\n ) {}\n\n /**\n * Map option attributes that can change over time\n */\n private static _mapOptionsAttributes: Array<string> = [\n 'disableDoubleClickZoom',\n 'scrollwheel',\n 'draggable',\n 'draggableCursor',\n 'draggingCursor',\n 'keyboardShortcuts',\n 'zoomControl',\n 'zoomControlOptions',\n 'styles',\n 'streetViewControl',\n 'streetViewControlOptions',\n 'zoom',\n 'mapTypeControl',\n 'mapTypeControlOptions',\n 'minZoom',\n 'maxZoom',\n 'panControl',\n 'panControlOptions',\n 'rotateControl',\n 'rotateControlOptions',\n 'fullscreenControl',\n 'fullscreenControlOptions',\n 'scaleControl',\n 'scaleControlOptions',\n 'mapTypeId',\n 'clickableIcons',\n 'gestureHandling',\n 'tilt',\n ];\n /**\n * The longitude that defines the center of the map.\n */\n @Input() public longitude: number = 0;\n\n /**\n * The latitude that defines the center of the map.\n */\n @Input() public latitude: number = 0;\n\n /**\n * The zoom level of the map. The default zoom level is 8.\n */\n @Input() public zoom: number = 8;\n\n /**\n * The minimal zoom level of the map allowed. When not provided, no restrictions to the zoom level\n * are enforced.\n */\n @Input() public minZoom?: number;\n\n /**\n * The maximal zoom level of the map allowed. When not provided, no restrictions to the zoom level\n * are enforced.\n */\n @Input() public maxZoom?: number;\n\n /**\n * Enables/disables if map is draggable.\n */\n @Input() public draggable: boolean = true;\n\n /**\n * Enables/disables zoom and center on double click. Enabled by default.\n */\n @Input() public disableDoubleClickZoom: boolean = false;\n\n /**\n * Enables/disables all default UI of the Google map. Please note: When the map is created, this\n * value cannot get updated.\n */\n @Input() public disableDefaultUI: boolean = false;\n\n /**\n * If false, disables scrollwheel zooming on the map. The scrollwheel is enabled by default.\n */\n @Input() public scrollwheel: boolean = true;\n\n /**\n * Color used for the background of the Map div. This color will be visible when tiles have not\n * yet loaded as the user pans. This option can only be set when the map is initialized.\n */\n @Input() public backgroundColor?: string;\n\n /**\n * The name or url of the cursor to display when mousing over a draggable map. This property uses\n * the css * cursor attribute to change the icon. As with the css property, you must specify at\n * least one fallback cursor that is not a URL. For example:\n * [draggableCursor]=\"'url(http://www.example.com/icon.png), auto;'\"\n */\n @Input() public draggableCursor?: string;\n\n /**\n * The name or url of the cursor to display when the map is being dragged. This property uses the\n * css cursor attribute to change the icon. As with the css property, you must specify at least\n * one fallback cursor that is not a URL. For example:\n * [draggingCursor]=\"'url(http://www.example.com/icon.png), auto;'\"\n */\n @Input() public draggingCursor?: string;\n\n /**\n * If false, prevents the map from being controlled by the keyboard. Keyboard shortcuts are\n * enabled by default.\n */\n @Input() public keyboardShortcuts: boolean = true;\n\n /**\n * The enabled/disabled state of the Zoom control.\n */\n @Input() public zoomControl: boolean = true;\n\n /**\n * Options for the Zoom control.\n */\n @Input() public zoomControlOptions?: google.maps.ZoomControlOptions;\n\n /**\n * Styles to apply to each of the default map types. Note that for Satellite/Hybrid and Terrain\n * modes, these styles will only apply to labels and geometry.\n */\n @Input() public styles: Array<google.maps.MapTypeStyle> = [];\n\n /**\n * When true and the latitude and/or longitude values changes, the Google Maps panTo method is\n * used to\n * center the map. See: https://developers.google.com/maps/documentation/javascript/reference#Map\n */\n @Input() public usePanning: boolean = false;\n\n /**\n * The initial enabled/disabled state of the Street View Pegman control.\n * This control is part of the default UI, and should be set to false when displaying a map type\n * on which the Street View road overlay should not appear (e.g. a non-Earth map type).\n */\n @Input() public streetViewControl?: boolean;\n\n /**\n * Options for the Street View control.\n */\n @Input()\n public streetViewControlOptions?: google.maps.StreetViewControlOptions;\n\n /**\n * Sets the viewport to contain the given bounds.\n * If this option to `true`, the bounds get automatically computed from all elements that use the {@link NgMapsFitBounds} directive.\n */\n @Input() public fitBounds: BoundsLiteral | boolean = false;\n\n /**\n * Padding amount for bounds. This optional parameter is undefined by default.\n */\n @Input() public boundsPadding?: number | Padding;\n\n /**\n * The initial enabled/disabled state of the Scale control. This is disabled by default.\n */\n @Input() public scaleControl: boolean = true;\n\n /**\n * Options for the scale control.\n */\n @Input() public scaleControlOptions?: google.maps.ScaleControlOptions;\n\n /**\n * The initial enabled/disabled state of the Map type control.\n */\n @Input() public mapTypeControl: boolean = true;\n\n /**\n * Options for the Map type control.\n */\n @Input() public mapTypeControlOptions?: google.maps.MapTypeControlOptions;\n\n /**\n * The initial enabled/disabled state of the Pan control.\n */\n @Input() public panControl: boolean = false;\n\n /**\n * Options for the Pan control.\n */\n @Input() public panControlOptions?: google.maps.PanControlOptions;\n\n /**\n * The initial enabled/disabled state of the Rotate control.\n */\n @Input() public rotateControl: boolean = false;\n\n /**\n * Options for the Rotate control.\n */\n @Input() public rotateControlOptions?: google.maps.RotateControlOptions;\n\n /**\n * The initial enabled/disabled state of the Fullscreen control.\n */\n @Input() public fullscreenControl: boolean = false;\n\n /**\n * Options for the Fullscreen control.\n */\n @Input()\n public fullscreenControlOptions?: google.maps.FullscreenControlOptions;\n\n /**\n * The map mapTypeId. Defaults to 'roadmap'.\n */\n @Input() public mapTypeId:\n | 'roadmap'\n | 'hybrid'\n | 'satellite'\n | 'terrain'\n | string\n | google.maps.MapTypeId = 'roadmap';\n\n /**\n * Add layers https://developers.google.com/maps/documentation/javascript/trafficlayer to map\n */\n @Input() public layers?: Array<LayerTypes> | LayerTypes;\n\n private _layerInstance: Map<\n LayerTypes,\n | google.maps.TrafficLayer\n | google.maps.TransitLayer\n | google.maps.BicyclingLayer\n > = new Map();\n\n /**\n * When false, map icons are not clickable. A map icon represents a point of interest,\n * also known as a POI. By default map icons are clickable.\n */\n @Input() public clickableIcons: boolean = true;\n\n /**\n * This setting controls how gestures on the map are handled.\n * Allowed values:\n * - 'cooperative' (Two-finger touch gestures pan and zoom the map. One-finger touch gestures are not handled by the map.)\n * - 'greedy' (All touch gestures pan or zoom the map.)\n * - 'none' (The map cannot be panned or zoomed by user gestures.)\n * - 'auto' [default] (Gesture handling is either cooperative or greedy, depending on whether the page is scrollable or not.\n */\n @Input() public gestureHandling: 'cooperative' | 'greedy' | 'none' | 'auto' =\n 'auto';\n\n /**\n * Controls the automatic switching behavior for the angle of incidence of\n * the map. The only allowed values are 0 and 45. The value 0 causes the map\n * to always use a 0° overhead view regardless of the zoom level and\n * viewport. The value 45 causes the tilt angle to automatically switch to\n * 45 whenever 45° imagery is available for the current zoom level and\n * viewport, and switch back to 0 whenever 45° imagery is not available\n * (this is the default behavior). 45° imagery is only available for\n * satellite and hybrid map types, within some locations, and at some zoom\n * levels. Note: getTilt returns the current tilt angle, not the value\n * specified by this option. Because getTilt and this option refer to\n * different things, do not bind() the tilt property; doing so may yield\n * unpredictable effects. (Default of AGM is 0 (disabled). Enable it with value 45.)\n */\n @Input() public tilt: number = 0;\n\n protected subscription: Subscription = new Subscription();\n protected _fitBoundsSubscription?: Subscription;\n\n /**\n * This event emitter gets emitted when the user clicks on the map (but not when they click on a\n * marker or infoWindow).\n */\n @Output()\n public mapClick: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event emitter gets emitted when the user right-clicks on the map (but not when they click\n * on a marker or infoWindow).\n */\n @Output()\n public mapRightClick: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event emitter gets emitted when the user double-clicks on the map (but not when they click\n * on a marker or infoWindow).\n */\n @Output()\n public mapDblClick: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event emitter is fired when the map center changes.\n */\n @Output()\n public centerChange: EventEmitter<GeoPoint> = new EventEmitter<GeoPoint>();\n\n /**\n * This event is fired when the viewport bounds have changed.\n */\n @Output()\n public boundsChange: EventEmitter<BoundsLiteral> =\n new EventEmitter<BoundsLiteral>();\n\n /**\n * This event is fired when the mapTypeId property changes.\n */\n @Output()\n public mapTypeIdChange: EventEmitter<google.maps.MapTypeId | string> =\n new EventEmitter<google.maps.MapTypeId | string>();\n\n /**\n * This event is fired when the map becomes idle after panning or zooming.\n */\n @Output() public idle: EventEmitter<void> = new EventEmitter<void>();\n\n /**\n * This event is fired when the zoom level has changed.\n */\n @Output()\n public zoomChange: EventEmitter<number> = new EventEmitter<number>();\n\n /**\n * This event is fired when the google map is fully initialized.\n * You get the google.maps.Map instance as a result of this EventEmitter.\n */\n @Output() public mapReady: EventEmitter<T> = new EventEmitter<T>();\n\n /**\n * This event is fired when the visible tiles have finished loading.\n */\n @Output() public tilesLoaded: EventEmitter<void> = new EventEmitter<void>();\n\n @ViewChild('container', { static: true }) public container?: ElementRef;\n\n /** @internal */\n public ngOnInit() {\n this._initMapInstance(this.container?.nativeElement);\n }\n\n protected async _initMapInstance(el: HTMLElement) {\n await this._mapsWrapper.createMap(\n el,\n { lat: this.latitude || 0, lng: this.longitude || 0 },\n {\n zoom: this.zoom,\n minZoom: this.minZoom,\n maxZoom: this.maxZoom,\n disableDefaultUI: this.disableDefaultUI,\n disableDoubleClickZoom: this.disableDoubleClickZoom,\n scrollwheel: this.scrollwheel,\n backgroundColor: this.backgroundColor,\n draggable: this.draggable,\n draggableCursor: this.draggableCursor,\n draggingCursor: this.draggingCursor,\n keyboardShortcuts: this.keyboardShortcuts,\n styles: this.styles,\n zoomControl: this.zoomControl,\n zoomControlOptions: this.zoomControlOptions,\n streetViewControl: this.streetViewControl,\n streetViewControlOptions: this.streetViewControlOptions,\n scaleControl: this.scaleControl,\n scaleControlOptions: this.scaleControlOptions,\n mapTypeControl: this.mapTypeControl,\n mapTypeControlOptions: this.mapTypeControlOptions,\n panControl: this.panControl,\n panControlOptions: this.panControlOptions,\n rotateControl: this.rotateControl,\n rotateControlOptions: this.rotateControlOptions,\n fullscreenControl: this.fullscreenControl,\n fullscreenControlOptions: this.fullscreenControlOptions,\n mapTypeId: this.mapTypeId as google.maps.MapTypeId,\n clickableIcons: this.clickableIcons,\n gestureHandling: this.gestureHandling,\n tilt: this.tilt,\n },\n );\n const map = await this._mapsWrapper.getNativeMap();\n this.mapReady.emit(map);\n\n // register event listeners\n this._handleMapCenterChange();\n this._handleMapZoomChange();\n this._handleMapMouseEvents();\n this._handleBoundsChange();\n this._handleMapTypeIdChange();\n this._handleTilesLoadedEvent();\n this._handleIdleEvent();\n }\n\n protected _handleIdleEvent() {\n throw new Error('Method not implemented.');\n }\n\n protected _handleTilesLoadedEvent() {\n throw new Error('Method not implemented.');\n }\n\n protected _handleMapTypeIdChange() {\n throw new Error('Method not implemented.');\n }\n\n protected _handleBoundsChange() {\n throw new Error('Method not implemented.');\n }\n\n protected _handleMapMouseEvents() {\n throw new Error('Method not implemented.');\n }\n\n protected _handleMapZoomChange() {\n throw new Error('Method not implemented.');\n }\n\n protected _handleMapCenterChange() {\n throw new Error('Method not implemented.');\n }\n\n /** @internal */\n public ngOnDestroy() {\n // unsubscribe all registered observable subscriptions\n this.subscription.unsubscribe();\n\n // remove all listeners from the map instance\n this._mapsWrapper.clearInstanceListeners();\n if (this._fitBoundsSubscription) {\n this._fitBoundsSubscription.unsubscribe();\n }\n }\n\n /* @internal */\n public ngOnChanges(changes: SimpleChanges) {\n this._updateMapOptionsChanges(changes);\n this._updatePosition(changes);\n this._layerChanges(changes);\n }\n\n protected _updateMapOptionsChanges(changes: SimpleChanges) {\n const options: SimpleChanges = {};\n const optionKeys = Object.keys(changes).filter((k) =>\n NgMapsViewComponent._mapOptionsAttributes.includes(k),\n );\n optionKeys.forEach((k) => {\n options[k] = changes[k].currentValue;\n });\n return this._mapsWrapper.setMapOptions(options);\n }\n\n /**\n * @todo google specific\n * @param changes\n * @protected\n */\n protected async _layerChanges(changes: SimpleChanges) {\n if (changes.layers) {\n const map = await this._mapsWrapper.getNativeMap();\n const layers = Array.isArray(this.layers) ? this.layers : [this.layers];\n layers.forEach((layer) => {\n if (layer && !this._layerInstance.has(layer)) {\n const i:\n | google.maps.TrafficLayer\n | google.maps.TransitLayer\n | google.maps.BicyclingLayer = new google.maps[layer]();\n // @todo typings\n i.setMap(map as any as google.maps.Map);\n this._layerInstance.set(layer, i);\n }\n });\n Array.from(this._layerInstance.keys()).forEach((layer) => {\n if (!layers.includes(layer)) {\n const i = this._layerInstance.get(layer);\n i?.setMap(null);\n this._layerInstance.delete(layer);\n }\n });\n }\n }\n\n /**\n * Triggers a resize event on the google map instance.\n * When recenter is true, the of the google map gets called with the current lat/lng values or fitBounds value to recenter the map.\n * Returns a promise that gets resolved after the event was triggered.\n */\n public triggerResize(recenter: boolean = true): Promise<void> {\n // Note: When we would trigger the resize event and show the map in the same turn (which is a\n // common case for triggering a resize event), then the resize event would not\n // work (to show the map), so we trigger the event in a timeout.\n return new Promise<void>((resolve) => {\n setTimeout(async () => {\n await this._mapsWrapper.triggerMapEvent('resize');\n if (recenter) {\n this.fitBounds != null ? this._fitBounds() : this._setCenter();\n }\n resolve();\n });\n });\n }\n\n protected async _updatePosition(changes: SimpleChanges) {\n if (\n changes.latitude == null &&\n changes.longitude == null &&\n !changes.fitBounds\n ) {\n // no position update needed\n return;\n }\n\n // we prefer fitBounds in changes\n if ('fitBounds' in changes) {\n await this._fitBounds();\n return;\n }\n if (typeof this.latitude === 'string') {\n this.latitude = parseFloat(this.latitude);\n }\n if (typeof this.longitude === 'string') {\n this.longitude = parseFloat(this.longitude);\n }\n const center = await this._mapsWrapper.getCenter();\n if (\n !(\n typeof this.latitude !== 'number' || typeof this.longitude !== 'number'\n ) &&\n this.latitude !== center?.lat &&\n this.longitude !== center?.lng\n ) {\n await this._setCenter();\n return;\n }\n }\n\n protected _setCenter() {\n const newCenter = {\n lat: this.latitude,\n lng: this.longitude,\n };\n if (this.usePanning) {\n return this._mapsWrapper.panTo(newCenter);\n } else {\n return this._mapsWrapper.setCenter(newCenter);\n }\n }\n\n protected async _fitBounds() {\n switch (this.fitBounds) {\n case true:\n this._subscribeToFitBoundsUpdates();\n break;\n case false:\n if (this._fitBoundsSubscription) {\n this._fitBoundsSubscription.unsubscribe();\n }\n break;\n default:\n if (this._fitBoundsSubscription) {\n this._fitBoundsSubscription.unsubscribe();\n }\n return this._updateBounds(this.fitBounds);\n }\n }\n\n protected _subscribeToFitBoundsUpdates() {\n this._zone.runOutsideAngular(() => {\n this._fitBoundsSubscription = this._fitBoundsService\n .getBounds$()\n .subscribe((b) => this._zone.run(() => this._updateBounds(b)));\n });\n }\n\n protected async _updateBounds(bounds: BoundsLiteral) {\n if (bounds != null) {\n /**\n * await map to not update bounds till map is initialized\n */\n await this._mapsWrapper.getNativeMap();\n if (this.usePanning) {\n return this._mapsWrapper.panToBounds(bounds, this.boundsPadding);\n } else {\n return this._mapsWrapper.fitBounds(bounds, this.boundsPadding);\n }\n }\n }\n}\n","import { Injectable, NgZone } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nimport { NgMapsMarkerComponent } from '../../directives/marker';\nimport { MapsApiWrapper } from '../maps-api-wrapper';\n\n@Injectable()\nexport abstract class MarkerManager<T = any> {\n protected _markers: Map<NgMapsMarkerComponent, T> = new Map<\n NgMapsMarkerComponent,\n T\n >();\n\n constructor(\n protected _mapsWrapper: MapsApiWrapper,\n protected _zone: NgZone,\n ) {}\n\n public abstract deleteMarker(marker: NgMapsMarkerComponent): void;\n\n public abstract updateMarkerPosition(marker: NgMapsMarkerComponent): void;\n\n public abstract updateTitle(marker: NgMapsMarkerComponent): void;\n\n public abstract updateLabel(marker: NgMapsMarkerComponent): void;\n\n public abstract updateDraggable(marker: NgMapsMarkerComponent): void;\n\n public abstract updateIconLegacy(marker: NgMapsMarkerComponent): void;\n public abstract updateIcon(marker: NgMapsMarkerComponent): void;\n\n public abstract updateOpacity(marker: NgMapsMarkerComponent): void;\n\n public abstract updateVisible(marker: NgMapsMarkerComponent): void;\n\n public abstract updateZIndex(marker: NgMapsMarkerComponent): void;\n\n public abstract updateClickable(marker: NgMapsMarkerComponent): void;\n\n public abstract updateAnimation(marker: NgMapsMarkerComponent): void;\n\n public async addMarker(marker: NgMapsMarkerComponent): Promise<void> {\n if (\n typeof marker.latitude !== 'number' ||\n typeof marker.longitude !== 'number'\n ) {\n return;\n }\n const m = await this._mapsWrapper.createMarker(\n { lat: marker.latitude, lng: marker.longitude },\n // TODO typings\n {\n label: marker.label,\n draggable: marker.draggable,\n icon: marker.icon ?? marker.iconUrl,\n opacity: marker.opacity,\n optimized: marker.optimized,\n visible: marker.visible,\n zIndex: marker.zIndex,\n title: marker.title,\n clickable: marker.clickable,\n animation:\n typeof marker.animation === 'string'\n ? google.maps.Animation[marker.animation]\n : marker.animation,\n } as any,\n );\n this._markers.set(marker, m);\n }\n\n public getNativeMarker(marker: NgMapsMarkerComponent): T | undefined {\n return this._markers.get(marker);\n }\n\n public abstract createEventObservable<E>(\n eventName: string | Array<string>,\n marker: NgMapsMarkerComponent,\n ): Observable<E>;\n}\n","import { Injectable, NgZone } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nimport { NgMapsInfoWindowComponent } from '../../directives/info-window';\nimport { MapsApiWrapper } from '../maps-api-wrapper';\n\nimport { MarkerManager } from './marker.manager';\n\n@Injectable()\nexport abstract class InfoWindowManager<T> {\n protected _infoWindows: Map<NgMapsInfoWindowComponent, T> = new Map();\n\n constructor(\n protected _mapsWrapper: MapsApiWrapper,\n protected _zone: NgZone,\n protected _markerManager: MarkerManager,\n ) {}\n\n public abstract deleteInfoWindow(\n infoWindow: NgMapsInfoWindowComponent,\n ): Promise<void>;\n\n public abstract setPosition(infoWindow: NgMapsInfoWindowComponent): void;\n\n public abstract setZIndex(infoWindow: NgMapsInfoWindowComponent): void;\n\n public abstract open(\n infoWindow: NgMapsInfoWindowComponent,\n event?: any,\n ): Promise<void>;\n\n public abstract close(infoWindow: NgMapsInfoWindowComponent): void;\n\n public abstract setOptions(\n infoWindow: NgMapsInfoWindowComponent,\n options: google.maps.InfoWindowOptions,\n ): Promise<void> | void;\n\n public abstract addInfoWindow(\n infoWindow: NgMapsInfoWindowComponent,\n ): Promise<void>;\n\n /**\n * Creates a Google Maps event listener for the given InfoWindow as an Observable\n */\n public abstract createEventObservable<E>(\n eventName: string,\n infoWindow: NgMapsInfoWindowComponent,\n ): Observable<E>;\n}\n","import {\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n SimpleChanges,\n ViewChild,\n} from '@angular/core';\n\nimport { InfoWindowManager } from '../services/managers/info-window.manager';\n\nimport { NgMapsMarkerComponent } from './marker';\n\nlet infoWindowId = 0;\n\n/**\n * NgMapsInfoWindowComponent renders a info window inside a {@link NgMapsMarkerComponent} or standalone.\n *\n * ### Example\n * ```typescript\n * import { Component } from '@angular/core';\n *\n * @Component({\n * selector: 'my-map-cmp',\n * styles: [`\n * map-view {\n * height: 300px;\n * }\n * `],\n * template: `\n * <map-view [latitude]=\"lat\" [longitude]=\"lng\" [zoom]=\"zoom\">\n * <map-marker [latitude]=\"lat\" [longitude]=\"lng\" [label]=\"'M'\">\n * <map-info-window [disableAutoPan]=\"true\">\n * Hi, this is the content of the <strong>info window</strong>\n * </map-info-window>\n * </map-marker>\n * </map-view>\n * `\n * })\n * ```\n */\n@Component({\n selector: 'map-info-window',\n template: `\n <div class=\"info-window-content\" #content>\n <ng-content></ng-content>\n </div>\n `,\n standalone: false,\n})\nexport class NgMapsInfoWindowComponent implements OnDestroy, OnChanges, OnInit {\n // @todo how to add correct typings?\n constructor(\n protected _infoWindowManager: InfoWindowManager<any>,\n public readonly elementRef: ElementRef<HTMLElement>,\n ) {}\n\n private static _infoWindowOptionsInputs: Array<string> = [\n 'disableAutoPan',\n 'maxWidth',\n ];\n /**\n * The latitude position of the info window (only usefull if you use it ouside of a {@link\n * NgMapsMarkerComponent}).\n */\n @Input() public latitude?: number;\n\n /**\n * The longitude position of the info window (only usefull if you use it ouside of a {@link\n * NgMapsMarkerComponent}).\n */\n @Input() public longitude?: number;\n\n /**\n * Disable auto-pan on open. By default, the info window will pan the map so that it is fully\n * visible when it opens.\n */\n @Input() public disableAutoPan?: boolean;\n\n /**\n * All InfoWindows are displayed on the map in order of their zIndex, with higher values\n * displaying in front of InfoWindows with lower values. By default, InfoWindows are displayed\n * according to their latitude, with InfoWindows of lower latitudes appearing in front of\n * InfoWindows at higher latitudes. InfoWindows are always displayed in front of markers.\n */\n @Input() public zIndex?: number;\n\n /**\n * Maximum width of the infowindow, regardless of content's width. This value is only considered\n * if it is set before a call to open. To change the maximum width when changing content, call\n * close, update maxWidth, and then open.\n */\n @Input() public maxWidth?: number;\n\n /**\n * Holds the marker that is the host of the info window (if available)\n */\n public hostMarker?: NgMapsMarkerComponent;\n\n /**\n * Holds the native element that is used for the info window content.\n */\n @ViewChild('content', { static: true })\n public content?: ElementRef;\n\n /**\n * Sets the open state for the InfoWindow. You can also call the open() and close() methods.\n */\n @Input() public isOpen: boolean = false;\n\n /**\n * Emits an event when the info window is closed.\n */\n @Output()\n public infoWindowClose: EventEmitter<void> = new EventEmitter<void>();\n private _infoWindowAddedToManager: boolean = false;\n private _id: string = (infoWindowId++).toString();\n\n public ngOnInit() {\n this._infoWindowManager.addInfoWindow(this).then(() => {\n this._infoWindowAddedToManager = true;\n this._updateOpenState();\n this._registerEventListeners();\n });\n }\n\n /** @internal */\n public ngOnChanges(changes: SimpleChanges) {\n if (!this._infoWindowAddedToManager) {\n return;\n }\n if (\n (changes.latitude || changes.longitude) &&\n typeof this.latitude === 'number' &&\n typeof this.longitude === 'number'\n ) {\n this._infoWindowManager.setPosition(this);\n }\n if (changes.zIndex) {\n this._infoWindowManager.setZIndex(this);\n }\n if (changes.isOpen) {\n this._updateOpenState();\n }\n this._setInfoWindowOptions(changes);\n }\n\n private _registerEventListeners() {\n this._infoWindowManager\n .createEventObservable('closeclick', this)\n .subscribe(() => {\n this.isOpen = false;\n this.infoWindowClose.emit();\n });\n }\n\n private _updateOpenState() {\n this.isOpen ? this.open() : this.close();\n }\n\n private _setInfoWindowOptions(changes: SimpleChanges) {\n const options: SimpleChanges = {};\n const optionKeys = Object.keys(changes).filter((k) =>\n NgMapsInfoWindowComponent._infoWindowOptionsInputs.includes(k),\n );\n optionKeys.forEach((k) => {\n options[k] = changes[k].currentValue;\n });\n this._infoWindowManager.setOptions(this, options);\n }\n\n /**\n * Opens the info window.\n */\n public open(event?: any): Promise<void> {\n return this._infoWindowManager.open(this, event);\n }\n\n /**\n * Closes the info window.\n */\n public async close(): Promise<void> {\n await this._infoWindowManager.close(this);\n return this.infoWindowClose.emit();\n }\n\n /** @internal */\n public id(): string {\n return this._id;\n }\n\n /** @internal */\n public toString(): string {\n return `NgMapsInfoWindowComponent-${this._id.toString()}`;\n }\n\n /** @internal */\n public ngOnDestroy() {\n this._infoWindowManager.deleteInfoWindow(this);\n }\n}\n","import {\n AfterContentInit,\n Component,\n ContentChildren,\n EventEmitter,\n forwardRef,\n Input,\n OnChanges,\n OnDestroy,\n Output,\n QueryList,\n SimpleChange,\n} from '@angular/core';\nimport { Observable, ReplaySubject, Subscription } from 'rxjs';\n\nimport { MarkerIcon } from '../interface/marker-icon';\nimport { MarkerOptions } from '../interface/marker-options';\nimport { FitBoundsAccessor, FitBoundsDetails } from '../services/fit-bounds';\nimport { MarkerManager } from '../services/managers/marker.manager';\n\nimport { NgMapsInfoWindowComponent } from './info-window';\n\nlet markerId = 0;\n\n/**\n * NgMapsMarkerComponent renders a map marker inside a {@link NgMapsViewComponent}.\n *\n * @example\n * <agm-map [latitude]=\"lat\" [longitude]=\"lng\" [zoom]=\"zoom\">\n * <agm-marker [latitude]=\"lat\" [longitude]=\"lng\" label=\"M\"></agm-marker>\n * </agm-map>\n */\n@Component({\n selector: 'map-marker',\n providers: [\n {\n provide: FitBoundsAccessor,\n useExisting: forwardRef(() => NgMapsMarkerComponent),\n },\n ],\n // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property\n inputs: [\n 'latitude',\n 'longitude',\n 'title',\n 'label',\n // eslint-disable-next-line @angular-eslint/no-input-rename\n 'draggable: markerDraggable',\n 'iconUrl',\n 'icon',\n 'openInfoWindow',\n 'opacity',\n 'optimized',\n 'visible',\n 'zIndex',\n 'animation',\n ],\n // eslint-disable-next-line @angular-eslint/no-outputs-metadata-property\n outputs: [\n 'markerClick',\n 'dragStart',\n // eslint-disable-next-line @angular-eslint/no-output-native\n 'drag',\n 'dragEnd',\n 'mouseOver',\n 'mouseOut',\n ],\n template: '<ng-content></ng-content>',\n standalone: false,\n})\nexport class NgMapsMarkerComponent\n implements\n OnDestroy,\n OnChanges,\n AfterContentInit,\n FitBoundsAccessor,\n MarkerOptions\n{\n /**\n * The latitude position of the marker.\n */\n @Input() public latitude?: number;\n\n /**\n * The longitude position of the marker.\n */\n @Input() public longitude?: number;\n\n /**\n * The title of the marker.\n */\n @Input() public title?: string;\n\n /**\n * The label (a single uppercase character) for the marker.\n */\n @Input() public label?: string | google.maps.MarkerLabel;\n\n /**\n * If true, the marker can be dragged. Default value is false.\n */\n // eslint-disable-next-line @angular-eslint/no-input-rename\n @Input('markerDraggable') public draggable: boolean = false;\n\n /**\n * Icon (the URL of the image) for the foreground.\n * Can also be a MarkerIcon (google.maps.Icon in Google Maps Javascript api)\n *\n * @see <a href=\"https://developers.google.com/maps/documentation/javascript/reference/marker#Icon\">google.maps.Icon</a>\n */\n @Input() public iconUrl?: string | google.maps.Icon;\n\n @Input() public icon:\n | MarkerIcon\n | string\n | google.maps.Icon\n | null\n | undefined\n | google.maps.Symbol = null;\n\n /**\n * If true, the marker is visible\n */\n @Input() public visible: boolean = true;\n\n /**\n * Whether to automatically open the child info window when the marker is clicked.\n */\n @Input() public openInfoWindow: boolean = true;\n\n /**\n * The marker's opacity between 0.0 and 1.0.\n */\n @Input() public opacity: number = 1;\n\n /**\n * Marker optimize flag. If it is false then it prevent duplicate rendering.\n * Default it is true\n */\n @Input() public optimized: boolean = true;\n\n /**\n * All markers are displayed on the map in order of their zIndex, with higher values displaying in\n * front of markers with lower values. By default, markers are displayed according to their\n * vertical position on screen, with lower markers appearing in front of markers further up the\n * screen.\n */\n @Input() public zIndex: number = 1;\n\n /**\n * If true, the marker can be clicked. Default value is true.\n */\n // eslint-disable-next-line @angular-eslint/no-input-rename\n @Input('markerClickable') public clickable: boolean = true;\n\n /**\n * Which animation to play when marker is added to a map.\n * This can be 'BOUNCE' or 'DROP'\n */\n public animation?: 'BOUNCE' | 'DROP' | null;\n\n /**\n * This event emitter gets emitted when the user clicks on the marker.\n */\n @Output()\n public markerClick: EventEmitter<NgMapsMarkerComponent> =\n new EventEmitter<NgMapsMarkerComponent>();\n\n /**\n * This event is fired when the user rightclicks on the marker.\n */\n @Output()\n public markerRightClick: EventEmitter<void> = new EventEmitter<void>();\n\n /**\n * This event is fired when the user starts dragging the marker.\n */\n @Output()\n public dragStart: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event is repeatedly fired while the user drags the marker.\n */\n @Output()\n // eslint-disable-next-line @angular-eslint/no-output-native\n public drag: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event is fired when the user stops dragging the marker.\n */\n @Output()\n public dragEnd: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event is fired when the user mouses over the marker.\n */\n @Output()\n public mouseOver: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * This event is fired when the user mouses outside the marker.\n */\n @Output()\n public mouseOut: EventEmitter<google.maps.MapMouseEvent> =\n new EventEmitter<google.maps.MapMouseEvent>();\n\n /**\n * @internal\n */\n @ContentChildren(NgMapsInfoWindowComponent)\n public infoWindow: QueryList<NgMapsInfoWindowComponent> =\n new QueryList<NgMapsInfoWindowComponent>();\n\n private _markerAddedToManger: boolean = false;\n private _id: string;\n private subscription: Subscription = new Subscription();\n\n protected readonly _fitBoundsDetails$: ReplaySubject<FitBoundsDetails> =\n new ReplaySubject<FitBoundsDetails>(1);\n\n constructor(private markerManager: MarkerManager) {\n this._id = (markerId++).toString();\n }\n\n /**\n * @internal\n */\n public ngAfterContentInit() {\n this.handleInfoWindowUpdate();\n this.infoWindow.changes.subscribe(() => this.handleInfoWindowUpdate());\n }\n\n private handleInfoWindowUpdate() {\n if (this.infoWindow.length > 1) {\n throw new Error('Expected no more than one info window.');\n }\n this.infoWindow.forEach((marker) => {\n marker.hostMarker = this;\n });\n }\n\n /**\n * @internal\n */\n public ngOnChanges(changes: { [key: string]: SimpleChange }) {\n if (typeof this.latitude === 'string') {\n this.latitude = Number(this.latitude);\n }\n if (typeof this.longitude === 'string') {\n this.longitude = Number(this.longitude);\n }\n if (\n typeof this.latitude !== 'number' ||\n typeof this.longitude !== 'number'\n ) {\n return;\n }\n if (!this._markerAddedToManger) {\n this.markerManager.addMarker(this).then(() => {\n this._updateFitBoundsDetails();\n this._markerAddedToManger = true;\n this._addEventListeners();\n });\n return;\n }\n if (changes.latitude || changes.longitude) {\n this.markerManager.updateMarkerPosition(this);\n this._updateFitBoundsDetails();\n }\n if (changes.title) {\n this.markerManager.updateTitle(this);\n }\n if (changes.label) {\n this.markerManager.updateLabel(this);\n }\n if (changes.draggable) {\n this.markerManager.updateDraggable(this);\n }\n if (changes.iconUrl) {\n this.markerManager.updateIconLegacy(this);\n }\n if (changes.icon) {\n this.markerManager.updateIcon(this);\n }\n if (changes.opacity) {\n this.markerManager.updateOpacity(this);\n }\n if (changes.visible) {\n this.markerManager.updateVisible(this);\n }\n if (changes.zIndex) {\n this.markerManager.updateZIndex(this);\n }\n if (changes.clickable) {\n this.markerManager.updateClickable(this);\n }\n if (changes.animation) {\n this.markerManager.updateAnimation(this);\n }\n }\n\n /**\n * @internal\n */\n public getFitBoundsDetails$(): Observable<FitBoundsDetails> {\n return this._fitBoundsDetails$.asObservable();\n }\n\n protected _updateFitBoundsDetails() {\n if (this.latitude && this.longitude) {\n this._fitBoundsDetails$.next({\n latLng: { lat: this.latitude, lng: this.longitude },\n });\n }\n }\n\n protected _addEventListeners() {\n const cs = this.markerManager\n .createEventObservable(['click', 'pointerdown'], this)\n .subscribe({\n next: (event) => {\n if (this.openInfoWindow) {\n this.infoWindow.forEach((infoWindow) => infoWindow.open(event));\n }\n this.markerClick.emit(this);\n },\n });\n this.subscription.add(cs);\n\n const rc = this.markerManager\n .createEventObservable('rightclick', this)\n .subscribe(() => {\n this.markerRightClick.emit();\n });\n this.subscription.add(rc);\n\n const ds = this.markerManager\n .createEventObservable<google.maps.MapMouseEvent>('dragstart', this)\n .subscribe((e: google.maps.MapMouseEvent) => {\n this.dragStart.emit(e);\n });\n this.subscription.add(ds);\n\n const d = this.markerManager\n .createEventObservable<google.maps.MapMouseEvent>('drag', this)\n .subscribe((e: google.maps.MapMouseEvent) => {\n this.drag.emit(e);\n });\n this.subscription.add(d);\n\n const dragend = this.markerManager\n .createEventObservable<google.maps.MapMouseEvent>('dragend', this)\n .subscribe((e: google.maps.MapMouseEvent) => {\n this.dragEnd.emit(e);\n });\n this.subscription.add(dragend);\n\n const mouseover = this.markerManager\n .createEventObservable<google.maps.MapMouseEvent>(\n ['mouseover', 'pointerenter'],\n this,\n )\n .subscribe((e: google.maps.MapMouseEvent) => {\n this.mouseOver.emit(e);\n });\n this.subscription.add(mouseover);\n\n const mouseout = this.markerManager\n .createEventObservable<google.maps.MapMouseEvent>(\n ['mouseout', 'pointerleave'],\n this,\n )\n .subscribe((e: google.maps.MapMouseEvent) => {\n this.mouseOut.emit(e);\n });\n this.subscription.add(mouseout);\n }\n\n /** @internal */\n public id(): string {\n return this._id;\n }\n\n /** @internal */\n public toString(): string {\n return `NgMapsMarker-${this._id}`;\n }\n\n /** @internal */\n public ngOnDestroy() {\n this.markerManager.deleteMarker(this);\n // unsubscribe all registered observable subscription\n this.subscription.unsubscribe();\n }\n}\n","import {\n Directive,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Self,\n SimpleChanges,\n} from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { distinctUntilChanged } from 'rxjs/operators';\n\nimport {\n FitBoundsAccessor,\n FitBoundsDetails,\n FitBoundsService,\n} from '../services/fit-bounds';\n\n/**\n * Adds the given directive to the auto fit bounds feature when the value is true.\n * To make it work with you custom AGM component, you also have to implement the {@link FitBoundsAccessor} abstract class.\n *\n * @example\n * <map-marker [mapFitBounds]=\"true\"></map-marker>\n */\n@Directive({\n selector: '[mapFitBounds]',\n standalone: false,\n})\nexport class NgMapsFitBoundsDirective implements OnInit, OnDestroy, OnChanges {\n /**\n * If the value is true, the element gets added to the bounds of the map.\n * Default: true.\n */\n @Input() public mapFitBounds: boolean = true;\n\n private _latestFitBoundsDetails: FitBoundsDetails | null = null;\n private subscription: Subscription = new Subscription();\n\n constructor(\n @Self() private readonly _fitBoundsAccessor: FitBoundsAccessor,\n private readonly _fitBoundsService: FitBoundsService,\n ) {}\n\n /**\n * @internal\n */\n public ngOnChanges(changes: SimpleChanges) {\n this._updateBounds();\n }\n\n /**\n * @internal\n */\n public ngOnInit() {\n this.subscription.add(\n this._fitBoundsAccessor\n .getFitBoundsDetails$()\n .pipe(\n distinctUntilChanged(\n (x: FitBoundsDetails, y: FitBoundsDetails) =>\n