UNPKG

@angular/google-maps

Version:
1,273 lines (1,265 loc) 165 kB
import * as i0 from '@angular/core'; import { inject, ElementRef, NgZone, EventEmitter, PLATFORM_ID, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, Output, Directive, InjectionToken, ContentChildren, NgModule, Injectable } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { BehaviorSubject, Observable, Subject, combineLatest, Subscription } from 'rxjs'; import { switchMap, take, map, takeUntil } from 'rxjs/operators'; /** Manages event on a Google Maps object, ensuring that events are added only when necessary. */ class MapEventManager { _ngZone; /** Pending listeners that were added before the target was set. */ _pending = []; _listeners = []; _targetStream = new BehaviorSubject(undefined); /** Clears all currently-registered event listeners. */ _clearListeners() { for (const listener of this._listeners) { listener.remove(); } this._listeners = []; } constructor(_ngZone) { this._ngZone = _ngZone; } /** Gets an observable that adds an event listener to the map when a consumer subscribes to it. */ getLazyEmitter(name) { return this._targetStream.pipe(switchMap(target => { const observable = new Observable(observer => { // If the target hasn't been initialized yet, cache the observer so it can be added later. if (!target) { this._pending.push({ observable, observer }); return undefined; } const listener = target.addListener(name, (event) => { this._ngZone.run(() => observer.next(event)); }); // If there's an error when initializing the Maps API (e.g. a wrong API key), it will // return a dummy object that returns `undefined` from `addListener` (see #26514). if (!listener) { observer.complete(); return undefined; } this._listeners.push(listener); return () => listener.remove(); }); return observable; })); } /** Sets the current target that the manager should bind events to. */ setTarget(target) { const currentTarget = this._targetStream.value; if (target === currentTarget) { return; } // Clear the listeners from the pre-existing target. if (currentTarget) { this._clearListeners(); this._pending = []; } this._targetStream.next(target); // Add the listeners that were bound before the map was initialized. this._pending.forEach(subscriber => subscriber.observable.subscribe(subscriber.observer)); this._pending = []; } /** Destroys the manager and clears the event listeners. */ destroy() { this._clearListeners(); this._pending = []; this._targetStream.complete(); } } // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 /** default options set to the Googleplex */ const DEFAULT_OPTIONS = { center: { lat: 37.421995, lng: -122.084092 }, zoom: 17, // Note: the type conversion here isn't necessary for our CI, but it resolves a g3 failure. mapTypeId: 'roadmap', }; /** Arbitrary default height for the map element */ const DEFAULT_HEIGHT = '500px'; /** Arbitrary default width for the map element */ const DEFAULT_WIDTH = '500px'; /** * Angular component that renders a Google Map via the Google Maps JavaScript * API. * @see https://developers.google.com/maps/documentation/javascript/reference/ */ class GoogleMap { _elementRef = inject(ElementRef); _ngZone = inject(NgZone); _eventManager = new MapEventManager(inject(NgZone)); _mapEl; _existingAuthFailureCallback; /** * The underlying google.maps.Map object * * See developers.google.com/maps/documentation/javascript/reference/map#Map */ googleMap; /** Whether we're currently rendering inside a browser. */ _isBrowser; /** Height of the map. Set this to `null` if you'd like to control the height through CSS. */ height = DEFAULT_HEIGHT; /** Width of the map. Set this to `null` if you'd like to control the width through CSS. */ width = DEFAULT_WIDTH; /** * The Map ID of the map. This parameter cannot be set or changed after a map is instantiated. * See: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.mapId */ mapId; /** * Type of map that should be rendered. E.g. hybrid map, terrain map etc. * See: https://developers.google.com/maps/documentation/javascript/reference/map#MapTypeId */ mapTypeId; set center(center) { this._center = center; } _center; set zoom(zoom) { this._zoom = zoom; } _zoom; set options(options) { this._options = options || DEFAULT_OPTIONS; } _options = DEFAULT_OPTIONS; /** Event emitted when the map is initialized. */ mapInitialized = new EventEmitter(); /** * See * https://developers.google.com/maps/documentation/javascript/events#auth-errors */ authFailure = new EventEmitter(); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.bounds_changed */ boundsChanged = this._eventManager.getLazyEmitter('bounds_changed'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.center_changed */ centerChanged = this._eventManager.getLazyEmitter('center_changed'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.click */ mapClick = this._eventManager.getLazyEmitter('click'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.dblclick */ mapDblclick = this._eventManager.getLazyEmitter('dblclick'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.drag */ mapDrag = this._eventManager.getLazyEmitter('drag'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.dragend */ mapDragend = this._eventManager.getLazyEmitter('dragend'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.dragstart */ mapDragstart = this._eventManager.getLazyEmitter('dragstart'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.heading_changed */ headingChanged = this._eventManager.getLazyEmitter('heading_changed'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.idle */ idle = this._eventManager.getLazyEmitter('idle'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.maptypeid_changed */ maptypeidChanged = this._eventManager.getLazyEmitter('maptypeid_changed'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.mousemove */ mapMousemove = this._eventManager.getLazyEmitter('mousemove'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.mouseout */ mapMouseout = this._eventManager.getLazyEmitter('mouseout'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.mouseover */ mapMouseover = this._eventManager.getLazyEmitter('mouseover'); /** * See * developers.google.com/maps/documentation/javascript/reference/map#Map.projection_changed */ projectionChanged = this._eventManager.getLazyEmitter('projection_changed'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.rightclick */ mapRightclick = this._eventManager.getLazyEmitter('rightclick'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.tilesloaded */ tilesloaded = this._eventManager.getLazyEmitter('tilesloaded'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.tilt_changed */ tiltChanged = this._eventManager.getLazyEmitter('tilt_changed'); /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.zoom_changed */ zoomChanged = this._eventManager.getLazyEmitter('zoom_changed'); constructor() { const platformId = inject(PLATFORM_ID); this._isBrowser = isPlatformBrowser(platformId); if (this._isBrowser) { const googleMapsWindow = window; if (!googleMapsWindow.google && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('Namespace google not found, cannot construct embedded google ' + 'map. Please install the Google Maps JavaScript API: ' + 'https://developers.google.com/maps/documentation/javascript/' + 'tutorial#Loading_the_Maps_API'); } this._existingAuthFailureCallback = googleMapsWindow.gm_authFailure; googleMapsWindow.gm_authFailure = () => { if (this._existingAuthFailureCallback) { this._existingAuthFailureCallback(); } this.authFailure.emit(); }; } } ngOnChanges(changes) { if (changes['height'] || changes['width']) { this._setSize(); } const googleMap = this.googleMap; if (googleMap) { if (changes['options']) { googleMap.setOptions(this._combineOptions()); } if (changes['center'] && this._center) { googleMap.setCenter(this._center); } // Note that the zoom can be zero. if (changes['zoom'] && this._zoom != null) { googleMap.setZoom(this._zoom); } if (changes['mapTypeId'] && this.mapTypeId) { googleMap.setMapTypeId(this.mapTypeId); } } } ngOnInit() { // It should be a noop during server-side rendering. if (this._isBrowser) { this._mapEl = this._elementRef.nativeElement.querySelector('.map-container'); this._setSize(); // Create the object outside the zone so its events don't trigger change detection. // We'll bring it back in inside the `MapEventManager` only for the events that the // user has subscribed to. if (google.maps.Map) { this._initialize(google.maps.Map); } else { this._ngZone.runOutsideAngular(() => { google.maps .importLibrary('maps') .then(lib => this._initialize(lib.Map)); }); } } } _initialize(mapConstructor) { this._ngZone.runOutsideAngular(() => { this.googleMap = new mapConstructor(this._mapEl, this._combineOptions()); this._eventManager.setTarget(this.googleMap); this.mapInitialized.emit(this.googleMap); }); } ngOnDestroy() { this.mapInitialized.complete(); this._eventManager.destroy(); if (this._isBrowser) { const googleMapsWindow = window; googleMapsWindow.gm_authFailure = this._existingAuthFailureCallback; } } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.fitBounds */ fitBounds(bounds, padding) { this._assertInitialized(); this.googleMap.fitBounds(bounds, padding); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.panBy */ panBy(x, y) { this._assertInitialized(); this.googleMap.panBy(x, y); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.panTo */ panTo(latLng) { this._assertInitialized(); this.googleMap.panTo(latLng); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.panToBounds */ panToBounds(latLngBounds, padding) { this._assertInitialized(); this.googleMap.panToBounds(latLngBounds, padding); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getBounds */ getBounds() { this._assertInitialized(); return this.googleMap.getBounds() || null; } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getCenter */ getCenter() { this._assertInitialized(); return this.googleMap.getCenter(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getClickableIcons */ getClickableIcons() { this._assertInitialized(); return this.googleMap.getClickableIcons(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getHeading */ getHeading() { this._assertInitialized(); return this.googleMap.getHeading(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getMapTypeId */ getMapTypeId() { this._assertInitialized(); return this.googleMap.getMapTypeId(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getProjection */ getProjection() { this._assertInitialized(); return this.googleMap.getProjection() || null; } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getStreetView */ getStreetView() { this._assertInitialized(); return this.googleMap.getStreetView(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getTilt */ getTilt() { this._assertInitialized(); return this.googleMap.getTilt(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.getZoom */ getZoom() { this._assertInitialized(); return this.googleMap.getZoom(); } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.controls */ get controls() { this._assertInitialized(); return this.googleMap.controls; } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.data */ get data() { this._assertInitialized(); return this.googleMap.data; } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.mapTypes */ get mapTypes() { this._assertInitialized(); return this.googleMap.mapTypes; } /** * See * https://developers.google.com/maps/documentation/javascript/reference/map#Map.overlayMapTypes */ get overlayMapTypes() { this._assertInitialized(); return this.googleMap.overlayMapTypes; } /** Returns a promise that resolves when the map has been initialized. */ _resolveMap() { return this.googleMap ? Promise.resolve(this.googleMap) : this.mapInitialized.pipe(take(1)).toPromise(); } _setSize() { if (this._mapEl) { const styles = this._mapEl.style; styles.height = this.height === null ? '' : coerceCssPixelValue(this.height) || DEFAULT_HEIGHT; styles.width = this.width === null ? '' : coerceCssPixelValue(this.width) || DEFAULT_WIDTH; } } /** Combines the center and zoom and the other map options into a single object */ _combineOptions() { const options = this._options || {}; return { ...options, // It's important that we set **some** kind of `center` and `zoom`, otherwise // Google Maps will render a blank rectangle which looks broken. center: this._center || options.center || DEFAULT_OPTIONS.center, zoom: this._zoom ?? options.zoom ?? DEFAULT_OPTIONS.zoom, // Passing in an undefined `mapTypeId` seems to break tile loading // so make sure that we have some kind of default (see #22082). mapTypeId: this.mapTypeId || options.mapTypeId || DEFAULT_OPTIONS.mapTypeId, mapId: this.mapId || options.mapId, }; } /** Asserts that the map has been initialized. */ _assertInitialized() { if (!this.googleMap && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw Error('Cannot access Google Map information before the API has been initialized. ' + 'Please wait for the API to load before trying to interact with it.'); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: GoogleMap, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.0", type: GoogleMap, isStandalone: true, selector: "google-map", inputs: { height: "height", width: "width", mapId: "mapId", mapTypeId: "mapTypeId", center: "center", zoom: "zoom", options: "options" }, outputs: { mapInitialized: "mapInitialized", authFailure: "authFailure", boundsChanged: "boundsChanged", centerChanged: "centerChanged", mapClick: "mapClick", mapDblclick: "mapDblclick", mapDrag: "mapDrag", mapDragend: "mapDragend", mapDragstart: "mapDragstart", headingChanged: "headingChanged", idle: "idle", maptypeidChanged: "maptypeidChanged", mapMousemove: "mapMousemove", mapMouseout: "mapMouseout", mapMouseover: "mapMouseover", projectionChanged: "projectionChanged", mapRightclick: "mapRightclick", tilesloaded: "tilesloaded", tiltChanged: "tiltChanged", zoomChanged: "zoomChanged" }, exportAs: ["googleMap"], usesOnChanges: true, ngImport: i0, template: '<div class="map-container"></div><ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: GoogleMap, decorators: [{ type: Component, args: [{ selector: 'google-map', exportAs: 'googleMap', changeDetection: ChangeDetectionStrategy.OnPush, template: '<div class="map-container"></div><ng-content />', encapsulation: ViewEncapsulation.None, }] }], ctorParameters: () => [], propDecorators: { height: [{ type: Input }], width: [{ type: Input }], mapId: [{ type: Input }], mapTypeId: [{ type: Input }], center: [{ type: Input }], zoom: [{ type: Input }], options: [{ type: Input }], mapInitialized: [{ type: Output }], authFailure: [{ type: Output }], boundsChanged: [{ type: Output }], centerChanged: [{ type: Output }], mapClick: [{ type: Output }], mapDblclick: [{ type: Output }], mapDrag: [{ type: Output }], mapDragend: [{ type: Output }], mapDragstart: [{ type: Output }], headingChanged: [{ type: Output }], idle: [{ type: Output }], maptypeidChanged: [{ type: Output }], mapMousemove: [{ type: Output }], mapMouseout: [{ type: Output }], mapMouseover: [{ type: Output }], projectionChanged: [{ type: Output }], mapRightclick: [{ type: Output }], tilesloaded: [{ type: Output }], tiltChanged: [{ type: Output }], zoomChanged: [{ type: Output }] } }); const cssUnitsPattern = /([A-Za-z%]+)$/; /** Coerces a value to a CSS pixel value. */ function coerceCssPixelValue(value) { if (value == null) { return ''; } return cssUnitsPattern.test(value) ? value : `${value}px`; } // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 class MapBaseLayer { _map = inject(GoogleMap); _ngZone = inject(NgZone); constructor() { } ngOnInit() { if (this._map._isBrowser) { this._ngZone.runOutsideAngular(() => { this._initializeObject(); }); this._assertInitialized(); this._setMap(); } } ngOnDestroy() { this._unsetMap(); } _assertInitialized() { if (!this._map.googleMap) { throw Error('Cannot access Google Map information before the API has been initialized. ' + 'Please wait for the API to load before trying to interact with it.'); } } _initializeObject() { } _setMap() { } _unsetMap() { } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapBaseLayer, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.0", type: MapBaseLayer, isStandalone: true, selector: "map-base-layer", exportAs: ["mapBaseLayer"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapBaseLayer, decorators: [{ type: Directive, args: [{ selector: 'map-base-layer', exportAs: 'mapBaseLayer', }] }], ctorParameters: () => [] }); // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 /** * Angular component that renders a Google Maps Bicycling Layer via the Google Maps JavaScript API. * * See developers.google.com/maps/documentation/javascript/reference/map#BicyclingLayer */ class MapBicyclingLayer { _map = inject(GoogleMap); _zone = inject(NgZone); /** * The underlying google.maps.BicyclingLayer object. * * See developers.google.com/maps/documentation/javascript/reference/map#BicyclingLayer */ bicyclingLayer; /** Event emitted when the bicycling layer is initialized. */ bicyclingLayerInitialized = new EventEmitter(); ngOnInit() { if (this._map._isBrowser) { if (google.maps.BicyclingLayer && this._map.googleMap) { this._initialize(this._map.googleMap, google.maps.BicyclingLayer); } else { this._zone.runOutsideAngular(() => { Promise.all([this._map._resolveMap(), google.maps.importLibrary('maps')]).then(([map, lib]) => { this._initialize(map, lib.BicyclingLayer); }); }); } } } _initialize(map, layerConstructor) { this._zone.runOutsideAngular(() => { this.bicyclingLayer = new layerConstructor(); this.bicyclingLayerInitialized.emit(this.bicyclingLayer); this._assertLayerInitialized(); this.bicyclingLayer.setMap(map); }); } ngOnDestroy() { this.bicyclingLayer?.setMap(null); } _assertLayerInitialized() { if (!this.bicyclingLayer) { throw Error('Cannot interact with a Google Map Bicycling Layer before it has been initialized. ' + 'Please wait for the Transit Layer to load before trying to interact with it.'); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapBicyclingLayer, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.0", type: MapBicyclingLayer, isStandalone: true, selector: "map-bicycling-layer", outputs: { bicyclingLayerInitialized: "bicyclingLayerInitialized" }, exportAs: ["mapBicyclingLayer"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapBicyclingLayer, decorators: [{ type: Directive, args: [{ selector: 'map-bicycling-layer', exportAs: 'mapBicyclingLayer', }] }], propDecorators: { bicyclingLayerInitialized: [{ type: Output }] } }); // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 /** * Angular component that renders a Google Maps Circle via the Google Maps JavaScript API. * @see developers.google.com/maps/documentation/javascript/reference/polygon#Circle */ class MapCircle { _map = inject(GoogleMap); _ngZone = inject(NgZone); _eventManager = new MapEventManager(inject(NgZone)); _options = new BehaviorSubject({}); _center = new BehaviorSubject(undefined); _radius = new BehaviorSubject(undefined); _destroyed = new Subject(); /** * Underlying google.maps.Circle object. * * @see developers.google.com/maps/documentation/javascript/reference/polygon#Circle */ circle; // initialized in ngOnInit set options(options) { this._options.next(options || {}); } set center(center) { this._center.next(center); } set radius(radius) { this._radius.next(radius); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.center_changed */ centerChanged = this._eventManager.getLazyEmitter('center_changed'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.click */ circleClick = this._eventManager.getLazyEmitter('click'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.dblclick */ circleDblclick = this._eventManager.getLazyEmitter('dblclick'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.drag */ circleDrag = this._eventManager.getLazyEmitter('drag'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.dragend */ circleDragend = this._eventManager.getLazyEmitter('dragend'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.dragstart */ circleDragstart = this._eventManager.getLazyEmitter('dragstart'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.mousedown */ circleMousedown = this._eventManager.getLazyEmitter('mousedown'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.mousemove */ circleMousemove = this._eventManager.getLazyEmitter('mousemove'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.mouseout */ circleMouseout = this._eventManager.getLazyEmitter('mouseout'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.mouseover */ circleMouseover = this._eventManager.getLazyEmitter('mouseover'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.mouseup */ circleMouseup = this._eventManager.getLazyEmitter('mouseup'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.radius_changed */ radiusChanged = this._eventManager.getLazyEmitter('radius_changed'); /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.rightclick */ circleRightclick = this._eventManager.getLazyEmitter('rightclick'); /** Event emitted when the circle is initialized. */ circleInitialized = new EventEmitter(); constructor() { } ngOnInit() { if (!this._map._isBrowser) { return; } this._combineOptions() .pipe(take(1)) .subscribe(options => { if (google.maps.Circle && this._map.googleMap) { this._initialize(this._map.googleMap, google.maps.Circle, options); } else { this._ngZone.runOutsideAngular(() => { Promise.all([this._map._resolveMap(), google.maps.importLibrary('maps')]).then(([map, lib]) => { this._initialize(map, lib.Circle, options); }); }); } }); } _initialize(map, circleConstructor, options) { // Create the object outside the zone so its events don't trigger change detection. // We'll bring it back in inside the `MapEventManager` only for the events that the // user has subscribed to. this._ngZone.runOutsideAngular(() => { this.circle = new circleConstructor(options); this._assertInitialized(); this.circle.setMap(map); this._eventManager.setTarget(this.circle); this.circleInitialized.emit(this.circle); this._watchForOptionsChanges(); this._watchForCenterChanges(); this._watchForRadiusChanges(); }); } ngOnDestroy() { this._eventManager.destroy(); this._destroyed.next(); this._destroyed.complete(); this.circle?.setMap(null); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.getBounds */ getBounds() { this._assertInitialized(); return this.circle.getBounds(); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.getCenter */ getCenter() { this._assertInitialized(); return this.circle.getCenter(); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.getDraggable */ getDraggable() { this._assertInitialized(); return this.circle.getDraggable(); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.getEditable */ getEditable() { this._assertInitialized(); return this.circle.getEditable(); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.getRadius */ getRadius() { this._assertInitialized(); return this.circle.getRadius(); } /** * @see * developers.google.com/maps/documentation/javascript/reference/polygon#Circle.getVisible */ getVisible() { this._assertInitialized(); return this.circle.getVisible(); } _combineOptions() { return combineLatest([this._options, this._center, this._radius]).pipe(map(([options, center, radius]) => { const combinedOptions = { ...options, center: center || options.center, radius: radius !== undefined ? radius : options.radius, }; return combinedOptions; })); } _watchForOptionsChanges() { this._options.pipe(takeUntil(this._destroyed)).subscribe(options => { this._assertInitialized(); this.circle.setOptions(options); }); } _watchForCenterChanges() { this._center.pipe(takeUntil(this._destroyed)).subscribe(center => { if (center) { this._assertInitialized(); this.circle.setCenter(center); } }); } _watchForRadiusChanges() { this._radius.pipe(takeUntil(this._destroyed)).subscribe(radius => { if (radius !== undefined) { this._assertInitialized(); this.circle.setRadius(radius); } }); } _assertInitialized() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this.circle) { throw Error('Cannot interact with a Google Map Circle before it has been ' + 'initialized. Please wait for the Circle to load before trying to interact with it.'); } } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapCircle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.0", type: MapCircle, isStandalone: true, selector: "map-circle", inputs: { options: "options", center: "center", radius: "radius" }, outputs: { centerChanged: "centerChanged", circleClick: "circleClick", circleDblclick: "circleDblclick", circleDrag: "circleDrag", circleDragend: "circleDragend", circleDragstart: "circleDragstart", circleMousedown: "circleMousedown", circleMousemove: "circleMousemove", circleMouseout: "circleMouseout", circleMouseover: "circleMouseover", circleMouseup: "circleMouseup", radiusChanged: "radiusChanged", circleRightclick: "circleRightclick", circleInitialized: "circleInitialized" }, exportAs: ["mapCircle"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapCircle, decorators: [{ type: Directive, args: [{ selector: 'map-circle', exportAs: 'mapCircle', }] }], ctorParameters: () => [], propDecorators: { options: [{ type: Input }], center: [{ type: Input }], radius: [{ type: Input }], centerChanged: [{ type: Output }], circleClick: [{ type: Output }], circleDblclick: [{ type: Output }], circleDrag: [{ type: Output }], circleDragend: [{ type: Output }], circleDragstart: [{ type: Output }], circleMousedown: [{ type: Output }], circleMousemove: [{ type: Output }], circleMouseout: [{ type: Output }], circleMouseover: [{ type: Output }], circleMouseup: [{ type: Output }], radiusChanged: [{ type: Output }], circleRightclick: [{ type: Output }], circleInitialized: [{ type: Output }] } }); // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 /** * Angular component that renders a Google Maps Directions Renderer via the Google Maps * JavaScript API. * * See developers.google.com/maps/documentation/javascript/reference/directions#DirectionsRenderer */ class MapDirectionsRenderer { _googleMap = inject(GoogleMap); _ngZone = inject(NgZone); _eventManager = new MapEventManager(inject(NgZone)); /** * See developers.google.com/maps/documentation/javascript/reference/directions * #DirectionsRendererOptions.directions */ set directions(directions) { this._directions = directions; } _directions; /** * See developers.google.com/maps/documentation/javascript/reference/directions * #DirectionsRendererOptions */ set options(options) { this._options = options; } _options; /** * See developers.google.com/maps/documentation/javascript/reference/directions * #DirectionsRenderer.directions_changed */ directionsChanged = this._eventManager.getLazyEmitter('directions_changed'); /** Event emitted when the directions renderer is initialized. */ directionsRendererInitialized = new EventEmitter(); /** The underlying google.maps.DirectionsRenderer object. */ directionsRenderer; constructor() { } ngOnInit() { if (this._googleMap._isBrowser) { if (google.maps.DirectionsRenderer && this._googleMap.googleMap) { this._initialize(this._googleMap.googleMap, google.maps.DirectionsRenderer); } else { this._ngZone.runOutsideAngular(() => { Promise.all([this._googleMap._resolveMap(), google.maps.importLibrary('routes')]).then(([map, lib]) => { this._initialize(map, lib.DirectionsRenderer); }); }); } } } _initialize(map, rendererConstructor) { // Create the object outside the zone so its events don't trigger change detection. // We'll bring it back in inside the `MapEventManager` only for the events that the // user has subscribed to. this._ngZone.runOutsideAngular(() => { this.directionsRenderer = new rendererConstructor(this._combineOptions()); this._assertInitialized(); this.directionsRenderer.setMap(map); this._eventManager.setTarget(this.directionsRenderer); this.directionsRendererInitialized.emit(this.directionsRenderer); }); } ngOnChanges(changes) { if (this.directionsRenderer) { if (changes['options']) { this.directionsRenderer.setOptions(this._combineOptions()); } if (changes['directions'] && this._directions !== undefined) { this.directionsRenderer.setDirections(this._directions); } } } ngOnDestroy() { this._eventManager.destroy(); this.directionsRenderer?.setMap(null); } /** * See developers.google.com/maps/documentation/javascript/reference/directions * #DirectionsRenderer.getDirections */ getDirections() { this._assertInitialized(); return this.directionsRenderer.getDirections(); } /** * See developers.google.com/maps/documentation/javascript/reference/directions * #DirectionsRenderer.getPanel */ getPanel() { this._assertInitialized(); return this.directionsRenderer.getPanel(); } /** * See developers.google.com/maps/documentation/javascript/reference/directions * #DirectionsRenderer.getRouteIndex */ getRouteIndex() { this._assertInitialized(); return this.directionsRenderer.getRouteIndex(); } _combineOptions() { const options = this._options || {}; return { ...options, directions: this._directions || options.directions, map: this._googleMap.googleMap, }; } _assertInitialized() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this.directionsRenderer) { throw Error('Cannot interact with a Google Map Directions Renderer before it has been ' + 'initialized. Please wait for the Directions Renderer to load before trying ' + 'to interact with it.'); } } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapDirectionsRenderer, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.0", type: MapDirectionsRenderer, isStandalone: true, selector: "map-directions-renderer", inputs: { directions: "directions", options: "options" }, outputs: { directionsChanged: "directionsChanged", directionsRendererInitialized: "directionsRendererInitialized" }, exportAs: ["mapDirectionsRenderer"], usesOnChanges: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapDirectionsRenderer, decorators: [{ type: Directive, args: [{ selector: 'map-directions-renderer', exportAs: 'mapDirectionsRenderer', }] }], ctorParameters: () => [], propDecorators: { directions: [{ type: Input }], options: [{ type: Input }], directionsChanged: [{ type: Output }], directionsRendererInitialized: [{ type: Output }] } }); // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 /** * Angular component that renders a Google Maps Ground Overlay via the Google Maps JavaScript API. * * See developers.google.com/maps/documentation/javascript/reference/image-overlay#GroundOverlay */ class MapGroundOverlay { _map = inject(GoogleMap); _ngZone = inject(NgZone); _eventManager = new MapEventManager(inject(NgZone)); _opacity = new BehaviorSubject(1); _url = new BehaviorSubject(''); _bounds = new BehaviorSubject(undefined); _destroyed = new Subject(); _hasWatchers; /** * The underlying google.maps.GroundOverlay object. * * See developers.google.com/maps/documentation/javascript/reference/image-overlay#GroundOverlay */ groundOverlay; /** URL of the image that will be shown in the overlay. */ set url(url) { this._url.next(url); } /** Bounds for the overlay. */ get bounds() { return this._bounds.value; } set bounds(bounds) { this._bounds.next(bounds); } /** Whether the overlay is clickable */ clickable = false; /** Opacity of the overlay. */ set opacity(opacity) { this._opacity.next(opacity); } /** * See * developers.google.com/maps/documentation/javascript/reference/image-overlay#GroundOverlay.click */ mapClick = this._eventManager.getLazyEmitter('click'); /** * See * developers.google.com/maps/documentation/javascript/reference/image-overlay * #GroundOverlay.dblclick */ mapDblclick = this._eventManager.getLazyEmitter('dblclick'); /** Event emitted when the ground overlay is initialized. */ groundOverlayInitialized = new EventEmitter(); constructor() { } ngOnInit() { if (this._map._isBrowser) { // The ground overlay setup is slightly different from the other Google Maps objects in that // we have to recreate the `GroundOverlay` object whenever the bounds change, because // Google Maps doesn't provide an API to update the bounds of an existing overlay. this._bounds.pipe(takeUntil(this._destroyed)).subscribe(bounds => { if (this.groundOverlay) { this.groundOverlay.setMap(null); this.groundOverlay = undefined; } if (!bounds) { return; } if (google.maps.GroundOverlay && this._map.googleMap) { this._initialize(this._map.googleMap, google.maps.GroundOverlay, bounds); } else { this._ngZone.runOutsideAngular(() => { Promise.all([this._map._resolveMap(), google.maps.importLibrary('maps')]).then(([map, lib]) => { this._initialize(map, lib.GroundOverlay, bounds); }); }); } }); } } _initialize(map, overlayConstructor, bounds) { // Create the object outside the zone so its events don't trigger change detection. // We'll bring it back in inside the `MapEventManager` only for the events that the // user has subscribed to. this._ngZone.runOutsideAngular(() => { this.groundOverlay = new overlayConstructor(this._url.getValue(), bounds, { clickable: this.clickable, opacity: this._opacity.value, }); this._assertInitialized(); this.groundOverlay.setMap(map); this._eventManager.setTarget(this.groundOverlay); this.groundOverlayInitialized.emit(this.groundOverlay); // We only need to set up the watchers once. if (!this._hasWatchers) { this._hasWatchers = true; this._watchForOpacityChanges(); this._watchForUrlChanges(); } }); } ngOnDestroy() { this._eventManager.destroy(); this._destroyed.next(); this._destroyed.complete(); this.groundOverlay?.setMap(null); } /** * See * developers.google.com/maps/documentation/javascript/reference/image-overlay * #GroundOverlay.getBounds */ getBounds() { this._assertInitialized(); return this.groundOverlay.getBounds(); } /** * See * developers.google.com/maps/documentation/javascript/reference/image-overlay * #GroundOverlay.getOpacity */ getOpacity() { this._assertInitialized(); return this.groundOverlay.getOpacity(); } /** * See * developers.google.com/maps/documentation/javascript/reference/image-overlay * #GroundOverlay.getUrl */ getUrl() { this._assertInitialized(); return this.groundOverlay.getUrl(); } _watchForOpacityChanges() { this._opacity.pipe(takeUntil(this._destroyed)).subscribe(opacity => { if (opacity != null) { this.groundOverlay?.setOpacity(opacity); } }); } _watchForUrlChanges() { this._url.pipe(takeUntil(this._destroyed)).subscribe(url => { const overlay = this.groundOverlay; if (overlay) { overlay.set('url', url); // Google Maps only redraws the overlay if we re-set the map. overlay.setMap(null); overlay.setMap(this._map.googleMap); } }); } _assertInitialized() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this.groundOverlay) { throw Error('Cannot interact with a Google Map GroundOverlay before it has been initialized. ' + 'Please wait for the GroundOverlay to load before trying to interact with it.'); } } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapGroundOverlay, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.0", type: MapGroundOverlay, isStandalone: true, selector: "map-ground-overlay", inputs: { url: "url", bounds: "bounds", clickable: "clickable", opacity: "opacity" }, outputs: { mapClick: "mapClick", mapDblclick: "mapDblclick", groundOverlayInitialized: "groundOverlayInitialized" }, exportAs: ["mapGroundOverlay"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: MapGroundOverlay, decorators: [{ type: Directive, args: [{ selector: 'map-ground-overlay', exportAs: 'mapGroundOverlay', }] }], ctorParameters: () => [], propDecorators: { url: [{ type: Input }], bounds: [{ type: Input }], clickable: [{ type: Input }], opacity: [{ type: Input }], mapClick: [{ type: Output }], mapDblclick: [{ type: Output }], groundOverlayInitialized: [{ type: Output }] } }); // Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265 /** * Angular component that renders a Google Maps info window via the Google Maps JavaScript API. * * See developers.google.com/maps/documentation/javascript/reference/info-window */ class MapInfoWindow { _googleMap = inject(GoogleMap); _elementRef = inject(ElementRef); _ngZone = inject(NgZone); _eventManager = new MapEventManager(inject(NgZone)); _options = new BehaviorSubject({}); _position = new BehaviorSubject(undefined); _destroy = new Subject(); /** * Underlying google.maps.InfoWindow * * See developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow */ infoWindow; set options(options) { this._options.next(options || {}); } set position(position) { this._position.next(position); } /** * See * developers.google.com/maps/documentation/javascript/reference/info-window#InfoWindow.closeclick */ closeclick = this._eventManager.getLazyEmitter('closeclick'); /** * See * developers.google.com/maps/documentation/javascript/reference/info-window * #InfoWindow.content_changed */ contentChanged = this._eventManager.getLaz