Skip to content

Commit b4b91be

Browse files
authored
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 10 additions & 0 deletions
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

Lines changed: 8 additions & 2 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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,
Lines changed: 182 additions & 0 deletions
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+
}

0 commit comments

Comments
 (0)