Skip to content

Commit b4b91be

Browse files
authoredFeb 8, 2024
feat(google-maps): add advanced marker (#28525)
* feat(google-maps): add advanced marker This commit introduces the advanced-marker feature to the map package, enabling users to add custom, interactive markers to their maps. Related #25897 * feat(google-maps): remove default value for mapId and correct z-index description property for advanced markers * feat(google-maps): generate api report file for google-maps
1 parent add3cd4 commit b4b91be

File tree

11 files changed

+563
-4
lines changed

11 files changed

+563
-4
lines changed
 

‎src/dev-app/google-map/google-map-demo.html

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
(mapClick)="clickMarker(marker)"></map-marker>
2121
}
2222
</map-marker-clusterer>
23+
<map-advanced-marker
24+
#secondMarker="mapAdvancedMarker"
25+
(mapClick)="clickAdvancedMarker(secondMarker)"
26+
title="Advanced Marker"
27+
[gmpDraggable]="false"
28+
[position]="mapAdvancedMarkerPosition"
29+
></map-advanced-marker>
2330
<map-info-window>Testing 1 2 3</map-info-window>
2431
@if (isPolylineDisplayed) {
2532
<map-polyline [options]="polylineOptions"></map-polyline>

‎src/dev-app/google-map/google-map-demo.ts

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
MapRectangle,
2626
MapTrafficLayer,
2727
MapTransitLayer,
28+
MapAdvancedMarker,
2829
} from '@angular/google-maps';
2930

3031
const POLYLINE_PATH: google.maps.LatLngLiteral[] = [
@@ -72,6 +73,7 @@ let apiLoadingPromise: Promise<unknown> | null = null;
7273
MapKmlLayer,
7374
MapMarker,
7475
MapMarkerClusterer,
76+
MapAdvancedMarker,
7577
MapPolygon,
7678
MapPolyline,
7779
MapRectangle,
@@ -87,6 +89,7 @@ export class GoogleMapDemo {
8789
@ViewChild(MapCircle) circle: MapCircle;
8890

8991
center = {lat: 24, lng: 12};
92+
mapAdvancedMarkerPosition = {lat: 24, lng: 16};
9093
markerOptions = {draggable: false};
9194
markerPositions: google.maps.LatLngLiteral[] = [];
9295
zoom = 4;
@@ -173,6 +176,13 @@ export class GoogleMapDemo {
173176
this.infoWindow.open(marker);
174177
}
175178

179+
clickAdvancedMarker(advancedMarker: MapAdvancedMarker) {
180+
this.infoWindow.openAdvancedMarkerElement(
181+
advancedMarker.advancedMarker,
182+
advancedMarker.advancedMarker.title,
183+
);
184+
}
185+
176186
handleRightclick() {
177187
this.markerPositions.pop();
178188
}

‎src/google-maps/google-map/google-map.spec.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ describe('GoogleMap', () => {
114114
});
115115

116116
it('sets center and zoom of the map', () => {
117-
const options = {center: {lat: 3, lng: 5}, zoom: 7, mapTypeId: DEFAULT_OPTIONS.mapTypeId};
117+
const options = {
118+
center: {lat: 3, lng: 5},
119+
zoom: 7,
120+
mapTypeId: DEFAULT_OPTIONS.mapTypeId,
121+
};
118122
mapSpy = createMapSpy(options);
119123
mapConstructorSpy = createMapConstructorSpy(mapSpy);
120124

@@ -140,6 +144,7 @@ describe('GoogleMap', () => {
140144
zoom: 7,
141145
draggable: false,
142146
mapTypeId: DEFAULT_OPTIONS.mapTypeId,
147+
mapId: '123',
143148
};
144149
mapSpy = createMapSpy(options);
145150
mapConstructorSpy = createMapConstructorSpy(mapSpy);
@@ -194,12 +199,13 @@ describe('GoogleMap', () => {
194199
});
195200

196201
it('gives precedence to center and zoom over options', () => {
197-
const inputOptions = {center: {lat: 3, lng: 5}, zoom: 7, heading: 170};
202+
const inputOptions = {center: {lat: 3, lng: 5}, zoom: 7, heading: 170, mapId: '123'};
198203
const correctedOptions = {
199204
center: {lat: 12, lng: 15},
200205
zoom: 5,
201206
heading: 170,
202207
mapTypeId: DEFAULT_OPTIONS.mapTypeId,
208+
mapId: '123',
203209
};
204210
mapSpy = createMapSpy(correctedOptions);
205211
mapConstructorSpy = createMapConstructorSpy(mapSpy);

‎src/google-maps/google-map/google-map.ts

+6
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
8383
/** Width of the map. Set this to `null` if you'd like to control the width through CSS. */
8484
@Input() width: string | number | null = DEFAULT_WIDTH;
8585

86+
/**
87+
* The Map ID of the map. This parameter cannot be set or changed after a map is instantiated.
88+
* See: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.mapId
89+
*/
90+
@Input() mapId: string | undefined;
91+
8692
/**
8793
* Type of map that should be rendered. E.g. hybrid map, terrain map etc.
8894
* See: https://developers.google.com/maps/documentation/javascript/reference/map#MapTypeId

‎src/google-maps/google-maps-module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {MapRectangle} from './map-rectangle/map-rectangle';
2424
import {MapTrafficLayer} from './map-traffic-layer/map-traffic-layer';
2525
import {MapTransitLayer} from './map-transit-layer/map-transit-layer';
2626
import {MapHeatmapLayer} from './map-heatmap-layer/map-heatmap-layer';
27+
import {MapAdvancedMarker} from './map-advanced-marker/map-advanced-marker';
2728

2829
const COMPONENTS = [
2930
GoogleMap,
@@ -36,6 +37,7 @@ const COMPONENTS = [
3637
MapInfoWindow,
3738
MapKmlLayer,
3839
MapMarker,
40+
MapAdvancedMarker,
3941
MapMarkerClusterer,
4042
MapPolygon,
4143
MapPolyline,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import {Component, ViewChild} from '@angular/core';
2+
import {TestBed, fakeAsync, flush} from '@angular/core/testing';
3+
4+
import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map';
5+
import {
6+
createAdvancedMarkerConstructorSpy,
7+
createAdvancedMarkerSpy,
8+
createMapConstructorSpy,
9+
createMapSpy,
10+
} from '../testing/fake-google-map-utils';
11+
import {DEFAULT_MARKER_OPTIONS, MapAdvancedMarker} from './map-advanced-marker';
12+
13+
describe('MapAdvancedMarker', () => {
14+
let mapSpy: jasmine.SpyObj<google.maps.Map>;
15+
16+
beforeEach(() => {
17+
mapSpy = createMapSpy(DEFAULT_OPTIONS);
18+
createMapConstructorSpy(mapSpy);
19+
});
20+
21+
afterEach(() => {
22+
(window.google as any) = undefined;
23+
});
24+
25+
it('initializes a Google Map advanced marker', fakeAsync(() => {
26+
const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS);
27+
const advancedMarkerConstructorSpy = createAdvancedMarkerConstructorSpy(advancedMarkerSpy);
28+
29+
const fixture = TestBed.createComponent(TestApp);
30+
fixture.detectChanges();
31+
flush();
32+
expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith({
33+
...DEFAULT_MARKER_OPTIONS,
34+
title: undefined,
35+
content: undefined,
36+
gmpDraggable: undefined,
37+
zIndex: undefined,
38+
map: mapSpy,
39+
});
40+
}));
41+
42+
it('sets advanced marker inputs', fakeAsync(() => {
43+
const options: google.maps.marker.AdvancedMarkerElementOptions = {
44+
position: {lat: 3, lng: 5},
45+
title: 'marker title',
46+
map: mapSpy,
47+
content: undefined,
48+
gmpDraggable: true,
49+
zIndex: 1,
50+
};
51+
const advancedMarkerSpy = createAdvancedMarkerSpy(options);
52+
const advancedMarkerConstructorSpy = createAdvancedMarkerConstructorSpy(advancedMarkerSpy);
53+
54+
const fixture = TestBed.createComponent(TestApp);
55+
fixture.componentInstance.position = options.position;
56+
fixture.componentInstance.title = options.title;
57+
fixture.componentInstance.content = options.content;
58+
fixture.componentInstance.gmpDraggable = options.gmpDraggable;
59+
fixture.componentInstance.zIndex = options.zIndex;
60+
61+
fixture.detectChanges();
62+
flush();
63+
64+
expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith(options);
65+
}));
66+
67+
it('sets marker options, ignoring map', fakeAsync(() => {
68+
const options: google.maps.marker.AdvancedMarkerElementOptions = {
69+
position: {lat: 3, lng: 5},
70+
title: 'marker title',
71+
content: undefined,
72+
gmpDraggable: true,
73+
zIndex: 1,
74+
};
75+
const advancedMarkerSpy = createAdvancedMarkerSpy(options);
76+
const advancedMarkerConstructorSpy = createAdvancedMarkerConstructorSpy(advancedMarkerSpy);
77+
78+
const fixture = TestBed.createComponent(TestApp);
79+
fixture.componentInstance.options = options;
80+
fixture.detectChanges();
81+
flush();
82+
83+
expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith({...options, map: mapSpy});
84+
}));
85+
86+
it('gives precedence to specific inputs over options', fakeAsync(() => {
87+
const options: google.maps.marker.AdvancedMarkerElementOptions = {
88+
position: {lat: 3, lng: 5},
89+
title: 'marker title',
90+
content: undefined,
91+
gmpDraggable: true,
92+
zIndex: 1,
93+
};
94+
95+
const expectedOptions: google.maps.marker.AdvancedMarkerElementOptions = {
96+
position: {lat: 4, lng: 6},
97+
title: 'marker title 2',
98+
content: undefined,
99+
gmpDraggable: false,
100+
zIndex: 999,
101+
map: mapSpy,
102+
};
103+
const advancedMarkerSpy = createAdvancedMarkerSpy(options);
104+
const advancedMarkerConstructorSpy = createAdvancedMarkerConstructorSpy(advancedMarkerSpy);
105+
106+
const fixture = TestBed.createComponent(TestApp);
107+
fixture.componentInstance.position = expectedOptions.position;
108+
fixture.componentInstance.title = expectedOptions.title;
109+
fixture.componentInstance.content = expectedOptions.content;
110+
fixture.componentInstance.gmpDraggable = expectedOptions.gmpDraggable;
111+
fixture.componentInstance.zIndex = expectedOptions.zIndex;
112+
fixture.componentInstance.options = options;
113+
114+
fixture.detectChanges();
115+
flush();
116+
117+
expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith(expectedOptions);
118+
}));
119+
120+
it('initializes marker event handlers', fakeAsync(() => {
121+
const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS);
122+
createAdvancedMarkerConstructorSpy(advancedMarkerSpy);
123+
124+
const addSpy = advancedMarkerSpy.addListener;
125+
const fixture = TestBed.createComponent(TestApp);
126+
fixture.detectChanges();
127+
flush();
128+
129+
expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function));
130+
expect(addSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function));
131+
expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function));
132+
expect(addSpy).not.toHaveBeenCalledWith('dragstart', jasmine.any(Function));
133+
}));
134+
135+
it('should be able to add an event listener after init', fakeAsync(() => {
136+
const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS);
137+
createAdvancedMarkerConstructorSpy(advancedMarkerSpy);
138+
139+
const addSpy = advancedMarkerSpy.addListener;
140+
const fixture = TestBed.createComponent(TestApp);
141+
fixture.detectChanges();
142+
flush();
143+
144+
expect(addSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function));
145+
146+
// Pick an event that isn't bound in the template.
147+
const subscription = fixture.componentInstance.advancedMarker.mapDrag.subscribe();
148+
fixture.detectChanges();
149+
150+
expect(addSpy).toHaveBeenCalledWith('drag', jasmine.any(Function));
151+
subscription.unsubscribe();
152+
}));
153+
});
154+
155+
@Component({
156+
selector: 'test-app',
157+
template: `
158+
<google-map>
159+
<map-advanced-marker
160+
[title]="title"
161+
[position]="position"
162+
[content]="content"
163+
[gmpDraggable]="gmpDraggable"
164+
[zIndex]="zIndex"
165+
(mapClick)="handleClick()"
166+
[options]="options" />
167+
</google-map>
168+
`,
169+
standalone: true,
170+
imports: [GoogleMap, MapAdvancedMarker],
171+
})
172+
class TestApp {
173+
@ViewChild(MapAdvancedMarker) advancedMarker: MapAdvancedMarker;
174+
title?: string | null;
175+
position?: google.maps.LatLng | google.maps.LatLngLiteral | null;
176+
content?: Node | google.maps.marker.PinElement | null;
177+
gmpDraggable?: boolean | null;
178+
zIndex?: number | null;
179+
options: google.maps.marker.AdvancedMarkerElementOptions;
180+
181+
handleClick() {}
182+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
// Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265
10+
/// <reference types="google.maps" />
11+
12+
import {
13+
Input,
14+
OnDestroy,
15+
OnInit,
16+
Output,
17+
NgZone,
18+
Directive,
19+
OnChanges,
20+
SimpleChanges,
21+
inject,
22+
EventEmitter,
23+
} from '@angular/core';
24+
25+
import {GoogleMap} from '../google-map/google-map';
26+
import {MapEventManager} from '../map-event-manager';
27+
import {Observable} from 'rxjs';
28+
29+
/**
30+
* Default options for the Google Maps marker component. Displays a marker
31+
* at the Googleplex.
32+
*/
33+
export const DEFAULT_MARKER_OPTIONS = {
34+
position: {lat: 37.221995, lng: -122.184092},
35+
};
36+
37+
/**
38+
* Angular component that renders a Google Maps marker via the Google Maps JavaScript API.
39+
*
40+
* See developers.google.com/maps/documentation/javascript/reference/marker
41+
*/
42+
@Directive({
43+
selector: 'map-advanced-marker',
44+
exportAs: 'mapAdvancedMarker',
45+
standalone: true,
46+
})
47+
export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy {
48+
private _eventManager = new MapEventManager(inject(NgZone));
49+
50+
/**
51+
* Rollover text. If provided, an accessibility text (e.g. for use with screen readers) will be added to the AdvancedMarkerElement with the provided value.
52+
* See: https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions.title
53+
*/
54+
@Input()
55+
set title(title: string) {
56+
this._title = title;
57+
}
58+
private _title: string;
59+
60+
/**
61+
* Sets the AdvancedMarkerElement's position. An AdvancedMarkerElement may be constructed without a position, but will not be displayed until its position is provided - for example, by a user's actions or choices. An AdvancedMarkerElement's position can be provided by setting AdvancedMarkerElement.position if not provided at the construction.
62+
* Note: AdvancedMarkerElement with altitude is only supported on vector maps.
63+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions.position
64+
*/
65+
@Input()
66+
set position(
67+
position:
68+
| google.maps.LatLngLiteral
69+
| google.maps.LatLng
70+
| google.maps.LatLngAltitude
71+
| google.maps.LatLngAltitudeLiteral,
72+
) {
73+
this._position = position;
74+
}
75+
private _position: google.maps.LatLngLiteral | google.maps.LatLng;
76+
77+
/**
78+
* The DOM Element backing the visual of an AdvancedMarkerElement.
79+
* Note: AdvancedMarkerElement does not clone the passed-in DOM element. Once the DOM element is passed to an AdvancedMarkerElement, passing the same DOM element to another AdvancedMarkerElement will move the DOM element and cause the previous AdvancedMarkerElement to look empty.
80+
* See: https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions.content
81+
*/
82+
@Input()
83+
set content(content: Node | google.maps.marker.PinElement) {
84+
this._content = content;
85+
}
86+
private _content: Node;
87+
88+
/**
89+
* If true, the AdvancedMarkerElement can be dragged.
90+
* Note: AdvancedMarkerElement with altitude is not draggable.
91+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions.gmpDraggable
92+
*/
93+
@Input()
94+
set gmpDraggable(draggable: boolean) {
95+
this._draggable = draggable;
96+
}
97+
private _draggable: boolean;
98+
99+
/**
100+
* Options for constructing an AdvancedMarkerElement.
101+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions
102+
*/
103+
@Input()
104+
set options(options: google.maps.marker.AdvancedMarkerElementOptions) {
105+
this._options = options;
106+
}
107+
private _options: google.maps.marker.AdvancedMarkerElementOptions;
108+
109+
/**
110+
* AdvancedMarkerElements on the map are prioritized by zIndex, with higher values indicating higher display.
111+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions.zIndex
112+
*/
113+
@Input()
114+
set zIndex(zIndex: number) {
115+
this._zIndex = zIndex;
116+
}
117+
private _zIndex: number;
118+
119+
/**
120+
* This event is fired when the AdvancedMarkerElement element is clicked.
121+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElement.click
122+
*/
123+
@Output() readonly mapClick: Observable<google.maps.MapMouseEvent> =
124+
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('click');
125+
126+
/**
127+
* This event is repeatedly fired while the user drags the AdvancedMarkerElement.
128+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElement.drag
129+
*/
130+
@Output() readonly mapDrag: Observable<google.maps.MapMouseEvent> =
131+
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('drag');
132+
133+
/**
134+
* This event is fired when the user stops dragging the AdvancedMarkerElement.
135+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElement.dragend
136+
*/
137+
@Output() readonly mapDragend: Observable<google.maps.MapMouseEvent> =
138+
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('dragend');
139+
140+
/**
141+
* This event is fired when the user starts dragging the AdvancedMarkerElement.
142+
* https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElement.dragstart
143+
*/
144+
@Output() readonly mapDragstart: Observable<google.maps.MapMouseEvent> =
145+
this._eventManager.getLazyEmitter<google.maps.MapMouseEvent>('dragstart');
146+
147+
/** Event emitted when the marker is initialized. */
148+
@Output() readonly markerInitialized: EventEmitter<google.maps.marker.AdvancedMarkerElement> =
149+
new EventEmitter<google.maps.marker.AdvancedMarkerElement>();
150+
151+
/**
152+
* The underlying google.maps.marker.AdvancedMarkerElement object.
153+
*
154+
* See developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElement
155+
*/
156+
advancedMarker: google.maps.marker.AdvancedMarkerElement;
157+
158+
constructor(
159+
private readonly _googleMap: GoogleMap,
160+
private _ngZone: NgZone,
161+
) {}
162+
163+
ngOnInit() {
164+
if (!this._googleMap._isBrowser) {
165+
return;
166+
}
167+
if (google.maps.marker?.AdvancedMarkerElement && this._googleMap.googleMap) {
168+
this._initialize(this._googleMap.googleMap, google.maps.marker.AdvancedMarkerElement);
169+
} else {
170+
this._ngZone.runOutsideAngular(() => {
171+
Promise.all([this._googleMap._resolveMap(), google.maps.importLibrary('marker')]).then(
172+
([map, lib]) => {
173+
this._initialize(map, (lib as google.maps.MarkerLibrary).AdvancedMarkerElement);
174+
},
175+
);
176+
});
177+
}
178+
}
179+
180+
private _initialize(
181+
map: google.maps.Map,
182+
advancedMarkerConstructor: typeof google.maps.marker.AdvancedMarkerElement,
183+
) {
184+
// Create the object outside the zone so its events don't trigger change detection.
185+
// We'll bring it back in inside the `MapEventManager` only for the events that the
186+
// user has subscribed to.
187+
this._ngZone.runOutsideAngular(() => {
188+
this.advancedMarker = new advancedMarkerConstructor(this._combineOptions());
189+
this._assertInitialized();
190+
this.advancedMarker.map = map;
191+
this._eventManager.setTarget(this.advancedMarker);
192+
this.markerInitialized.next(this.advancedMarker);
193+
});
194+
}
195+
196+
ngOnChanges(changes: SimpleChanges) {
197+
const {advancedMarker, _content, _position, _title, _draggable, _zIndex} = this;
198+
if (advancedMarker) {
199+
if (changes['title']) {
200+
advancedMarker.title = _title;
201+
}
202+
203+
if (changes['content']) {
204+
advancedMarker.content = _content;
205+
}
206+
207+
if (changes['gmpDraggable']) {
208+
advancedMarker.gmpDraggable = _draggable;
209+
}
210+
211+
if (changes['content']) {
212+
advancedMarker.content = _content;
213+
}
214+
215+
if (changes['position']) {
216+
advancedMarker.position = _position;
217+
}
218+
219+
if (changes['zIndex']) {
220+
advancedMarker.zIndex = _zIndex;
221+
}
222+
}
223+
}
224+
225+
ngOnDestroy() {
226+
this.markerInitialized.complete();
227+
this._eventManager.destroy();
228+
}
229+
230+
/** Creates a combined options object using the passed-in options and the individual inputs. */
231+
private _combineOptions(): google.maps.marker.AdvancedMarkerElementOptions {
232+
const options = this._options || DEFAULT_MARKER_OPTIONS;
233+
return {
234+
...options,
235+
title: this._title || options.title,
236+
position: this._position || options.position,
237+
content: this._content || options.content,
238+
zIndex: this._zIndex ?? options.zIndex,
239+
gmpDraggable: this._draggable ?? options.gmpDraggable,
240+
map: this._googleMap.googleMap,
241+
};
242+
}
243+
244+
/** Asserts that the map has been initialized. */
245+
private _assertInitialized(): asserts this is {marker: google.maps.marker.AdvancedMarkerElement} {
246+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
247+
if (!this.advancedMarker) {
248+
throw Error(
249+
'Cannot interact with a Google Map Marker before it has been ' +
250+
'initialized. Please wait for the Marker to load before trying to interact with it.',
251+
);
252+
}
253+
}
254+
}
255+
}

‎src/google-maps/map-info-window/map-info-window.ts

+19
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,25 @@ export class MapInfoWindow implements OnInit, OnDestroy {
193193
return this.infoWindow.getZIndex();
194194
}
195195

196+
/**
197+
* Opens the MapInfoWindow using the provided AdvancedMarkerElement.
198+
*/
199+
openAdvancedMarkerElement(
200+
advancedMarkerElement: google.maps.marker.AdvancedMarkerElement,
201+
content?: string | Element | Text,
202+
): void {
203+
this._assertInitialized();
204+
if (!advancedMarkerElement) {
205+
return;
206+
}
207+
208+
this.infoWindow.close();
209+
if (content) {
210+
this.infoWindow.setContent(content);
211+
}
212+
this.infoWindow.open(this._googleMap.googleMap, advancedMarkerElement);
213+
}
214+
196215
/**
197216
* Opens the MapInfoWindow using the provided anchor. If the anchor is not set,
198217
* then the position property of the options input is used instead.

‎src/google-maps/public-api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export {MapGroundOverlay} from './map-ground-overlay/map-ground-overlay';
2121
export {MapInfoWindow} from './map-info-window/map-info-window';
2222
export {MapKmlLayer} from './map-kml-layer/map-kml-layer';
2323
export {MapMarker} from './map-marker/map-marker';
24+
export {MapAdvancedMarker} from './map-advanced-marker/map-advanced-marker';
2425
export {MapMarkerClusterer} from './map-marker-clusterer/map-marker-clusterer';
2526
export {MapPolygon} from './map-polygon/map-polygon';
2627
export {MapPolyline} from './map-polyline/map-polyline';

‎src/google-maps/testing/fake-google-map-utils.ts

+42
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export interface TestingWindow extends Window {
3636
visualization?: {
3737
HeatmapLayer?: jasmine.Spy;
3838
};
39+
marker?: {
40+
AdvancedMarkerElement?: jasmine.Spy;
41+
};
3942
Geocoder?: jasmine.Spy;
4043
};
4144
};
@@ -139,6 +142,45 @@ export function createMarkerConstructorSpy(
139142
return markerConstructorSpy;
140143
}
141144

145+
/** Creates a jasmine.SpyObj for a google.maps.marker.AdvancedMarkerElement */
146+
export function createAdvancedMarkerSpy(
147+
options: google.maps.marker.AdvancedMarkerElementOptions,
148+
): jasmine.SpyObj<google.maps.marker.AdvancedMarkerElement> {
149+
const advancedMarkerSpy = jasmine.createSpyObj('google.maps.marker.AdvancedMarkerElement', [
150+
'addListener',
151+
]);
152+
advancedMarkerSpy.addListener.and.returnValue({remove: () => {}});
153+
return advancedMarkerSpy;
154+
}
155+
156+
/** Creates a jasmine.Spy to watch for the constructor of a google.maps.marker.AdvancedMarkerElement */
157+
export function createAdvancedMarkerConstructorSpy(
158+
advancedMarkerSpy: jasmine.SpyObj<google.maps.marker.AdvancedMarkerElement>,
159+
): jasmine.Spy {
160+
// The spy target function cannot be an arrow-function as this breaks when created through `new`.
161+
const advancedMarkerConstructorSpy = jasmine
162+
.createSpy('Advanced Marker constructor', function () {
163+
return advancedMarkerSpy;
164+
})
165+
.and.callThrough();
166+
const testingWindow: TestingWindow = window;
167+
if (testingWindow.google && testingWindow.google.maps) {
168+
testingWindow.google.maps.marker = {
169+
'AdvancedMarkerElement': advancedMarkerConstructorSpy,
170+
};
171+
} else {
172+
testingWindow.google = {
173+
maps: {
174+
marker: {
175+
'AdvancedMarkerElement': advancedMarkerConstructorSpy,
176+
},
177+
},
178+
};
179+
}
180+
181+
return advancedMarkerConstructorSpy;
182+
}
183+
142184
/** Creates a jasmine.SpyObj for a google.maps.InfoWindow */
143185
export function createInfoWindowSpy(
144186
options: google.maps.InfoWindowOptions,

‎tools/public_api_guard/google-maps/google-maps.md

+31-2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
8686
readonly mapDrag: Observable<void>;
8787
readonly mapDragend: Observable<void>;
8888
readonly mapDragstart: Observable<void>;
89+
mapId: string | undefined;
8990
readonly mapInitialized: EventEmitter<google.maps.Map>;
9091
readonly mapMousemove: Observable<google.maps.MapMouseEvent>;
9192
readonly mapMouseout: Observable<google.maps.MapMouseEvent>;
@@ -115,7 +116,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy {
115116
set zoom(zoom: number);
116117
readonly zoomChanged: Observable<void>;
117118
// (undocumented)
118-
static ɵcmp: i0.ɵɵComponentDeclaration<GoogleMap, "google-map", ["googleMap"], { "height": { "alias": "height"; "required": false; }; "width": { "alias": "width"; "required": false; }; "mapTypeId": { "alias": "mapTypeId"; "required": false; }; "center": { "alias": "center"; "required": false; }; "zoom": { "alias": "zoom"; "required": false; }; "options": { "alias": "options"; "required": false; }; }, { "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"; }, never, ["*"], true, never>;
119+
static ɵcmp: i0.ɵɵComponentDeclaration<GoogleMap, "google-map", ["googleMap"], { "height": { "alias": "height"; "required": false; }; "width": { "alias": "width"; "required": false; }; "mapId": { "alias": "mapId"; "required": false; }; "mapTypeId": { "alias": "mapTypeId"; "required": false; }; "center": { "alias": "center"; "required": false; }; "zoom": { "alias": "zoom"; "required": false; }; "options": { "alias": "options"; "required": false; }; }, { "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"; }, never, ["*"], true, never>;
119120
// (undocumented)
120121
static ɵfac: i0.ɵɵFactoryDeclaration<GoogleMap, never>;
121122
}
@@ -127,12 +128,39 @@ export class GoogleMapsModule {
127128
// (undocumented)
128129
static ɵinj: i0.ɵɵInjectorDeclaration<GoogleMapsModule>;
129130
// (undocumented)
130-
static ɵmod: i0.ɵɵNgModuleDeclaration<GoogleMapsModule, never, [typeof i1.GoogleMap, typeof i2.MapBaseLayer, typeof i3.MapBicyclingLayer, typeof i4.MapCircle, typeof i5.MapDirectionsRenderer, typeof i6.MapGroundOverlay, typeof i7.MapHeatmapLayer, typeof i8.MapInfoWindow, typeof i9.MapKmlLayer, typeof i10.MapMarker, typeof i11.MapMarkerClusterer, typeof i12.MapPolygon, typeof i13.MapPolyline, typeof i14.MapRectangle, typeof i15.MapTrafficLayer, typeof i16.MapTransitLayer], [typeof i1.GoogleMap, typeof i2.MapBaseLayer, typeof i3.MapBicyclingLayer, typeof i4.MapCircle, typeof i5.MapDirectionsRenderer, typeof i6.MapGroundOverlay, typeof i7.MapHeatmapLayer, typeof i8.MapInfoWindow, typeof i9.MapKmlLayer, typeof i10.MapMarker, typeof i11.MapMarkerClusterer, typeof i12.MapPolygon, typeof i13.MapPolyline, typeof i14.MapRectangle, typeof i15.MapTrafficLayer, typeof i16.MapTransitLayer]>;
131+
static ɵmod: i0.ɵɵNgModuleDeclaration<GoogleMapsModule, never, [typeof i1.GoogleMap, typeof i2.MapBaseLayer, typeof i3.MapBicyclingLayer, typeof i4.MapCircle, typeof i5.MapDirectionsRenderer, typeof i6.MapGroundOverlay, typeof i7.MapHeatmapLayer, typeof i8.MapInfoWindow, typeof i9.MapKmlLayer, typeof i10.MapMarker, typeof i11.MapAdvancedMarker, typeof i12.MapMarkerClusterer, typeof i13.MapPolygon, typeof i14.MapPolyline, typeof i15.MapRectangle, typeof i16.MapTrafficLayer, typeof i17.MapTransitLayer], [typeof i1.GoogleMap, typeof i2.MapBaseLayer, typeof i3.MapBicyclingLayer, typeof i4.MapCircle, typeof i5.MapDirectionsRenderer, typeof i6.MapGroundOverlay, typeof i7.MapHeatmapLayer, typeof i8.MapInfoWindow, typeof i9.MapKmlLayer, typeof i10.MapMarker, typeof i11.MapAdvancedMarker, typeof i12.MapMarkerClusterer, typeof i13.MapPolygon, typeof i14.MapPolyline, typeof i15.MapRectangle, typeof i16.MapTrafficLayer, typeof i17.MapTransitLayer]>;
131132
}
132133

133134
// @public
134135
export type HeatmapData = google.maps.MVCArray<google.maps.LatLng | google.maps.visualization.WeightedLocation | google.maps.LatLngLiteral> | (google.maps.LatLng | google.maps.visualization.WeightedLocation | google.maps.LatLngLiteral)[];
135136

137+
// @public
138+
export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy {
139+
constructor(_googleMap: GoogleMap, _ngZone: NgZone);
140+
advancedMarker: google.maps.marker.AdvancedMarkerElement;
141+
set content(content: Node | google.maps.marker.PinElement);
142+
set gmpDraggable(draggable: boolean);
143+
readonly mapClick: Observable<google.maps.MapMouseEvent>;
144+
readonly mapDrag: Observable<google.maps.MapMouseEvent>;
145+
readonly mapDragend: Observable<google.maps.MapMouseEvent>;
146+
readonly mapDragstart: Observable<google.maps.MapMouseEvent>;
147+
readonly markerInitialized: EventEmitter<google.maps.marker.AdvancedMarkerElement>;
148+
// (undocumented)
149+
ngOnChanges(changes: SimpleChanges): void;
150+
// (undocumented)
151+
ngOnDestroy(): void;
152+
// (undocumented)
153+
ngOnInit(): void;
154+
set options(options: google.maps.marker.AdvancedMarkerElementOptions);
155+
set position(position: google.maps.LatLngLiteral | google.maps.LatLng | google.maps.LatLngAltitude | google.maps.LatLngAltitudeLiteral);
156+
set title(title: string);
157+
set zIndex(zIndex: number);
158+
// (undocumented)
159+
static ɵdir: i0.ɵɵDirectiveDeclaration<MapAdvancedMarker, "map-advanced-marker", ["mapAdvancedMarker"], { "title": { "alias": "title"; "required": false; }; "position": { "alias": "position"; "required": false; }; "content": { "alias": "content"; "required": false; }; "gmpDraggable": { "alias": "gmpDraggable"; "required": false; }; "options": { "alias": "options"; "required": false; }; "zIndex": { "alias": "zIndex"; "required": false; }; }, { "mapClick": "mapClick"; "mapDrag": "mapDrag"; "mapDragend": "mapDragend"; "mapDragstart": "mapDragstart"; "markerInitialized": "markerInitialized"; }, never, never, true, never>;
160+
// (undocumented)
161+
static ɵfac: i0.ɵɵFactoryDeclaration<MapAdvancedMarker, never>;
162+
}
163+
136164
// @public
137165
export interface MapAnchorPoint {
138166
// (undocumented)
@@ -364,6 +392,7 @@ export class MapInfoWindow implements OnInit, OnDestroy {
364392
// (undocumented)
365393
ngOnInit(): void;
366394
open(anchor?: MapAnchorPoint, shouldFocus?: boolean): void;
395+
openAdvancedMarkerElement(advancedMarkerElement: google.maps.marker.AdvancedMarkerElement, content?: string | Element | Text): void;
367396
// (undocumented)
368397
set options(options: google.maps.InfoWindowOptions);
369398
// (undocumented)

0 commit comments

Comments
 (0)
Please sign in to comment.