Skip to content

Commit 30de283

Browse files
mbehrlichjelbourn
authored andcommitted
feat(google-maps): Add map-info-window component (#17027)
Add component that implements the Google Maps JavaScript API InfoWindow that displays on the Google Map and can be anchored to a marker.
1 parent 0b72461 commit 30de283

File tree

13 files changed

+447
-12
lines changed

13 files changed

+447
-12
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
(mapMousemove)="handleMove($event)"
88
(mapRightclick)="handleRightclick()">
99
<map-marker></map-marker>
10-
<map-marker *ngFor="let markerPosition of markerPositions"
11-
[position]="markerPosition"
12-
[options]="markerOptions"
13-
(mapClick)="clickMarker($event)"></map-marker>
10+
<map-marker #marker
11+
*ngFor="let markerPosition of markerPositions"
12+
[position]="markerPosition"
13+
[options]="markerOptions"
14+
(mapClick)="clickMarker(marker)"></map-marker>
15+
<map-info-window [position]="infoWindowPosition">Testing 1 2 3</map-info-window>
1416
</google-map>
1517

1618
<div>Latitude: {{display?.lat}}</div>

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component} from '@angular/core';
109
import {HttpClient} from '@angular/common/http';
10+
import {Component, ViewChild} from '@angular/core';
11+
import {MapInfoWindow, MapMarker} from '@angular/google-maps';
1112

1213
/** Demo Component for @angular/google-maps/map */
1314
@Component({
@@ -16,11 +17,14 @@ import {HttpClient} from '@angular/common/http';
1617
templateUrl: 'google-map-demo.html',
1718
})
1819
export class GoogleMapDemo {
20+
@ViewChild(MapInfoWindow, {static: false}) infoWindow: MapInfoWindow;
21+
1922
isReady = false;
2023

2124
center = {lat: 24, lng: 12};
2225
markerOptions = {draggable: false};
2326
markerPositions: google.maps.LatLngLiteral[] = [];
27+
infoWindowPosition: google.maps.LatLngLiteral;
2428
zoom = 4;
2529
display?: google.maps.LatLngLiteral;
2630

@@ -39,9 +43,8 @@ export class GoogleMapDemo {
3943
this.display = event.latLng.toJSON();
4044
}
4145

42-
clickMarker(event: google.maps.MouseEvent) {
43-
console.log(this.markerOptions);
44-
this.markerOptions = {draggable: true};
46+
clickMarker(marker: MapMarker) {
47+
this.infoWindow.open(marker);
4548
}
4649

4750
handleRightclick() {

src/google-maps/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ng_module(
1313
deps = [
1414
"@npm//@angular/core",
1515
"@npm//@types/googlemaps",
16+
"@npm//rxjs",
1617
],
1718
)
1819

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export class GoogleMap implements OnChanges, OnInit, AfterContentInit, OnDestroy
191191
@ContentChildren(MapMarker) _markers: QueryList<MapMarker>;
192192

193193
private _mapEl: HTMLElement;
194-
private _googleMap!: UpdatedGoogleMap;
194+
_googleMap!: UpdatedGoogleMap;
195195

196196
private _googleMapChanges!: Observable<google.maps.Map>;
197197

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@
88

99
import {NgModule} from '@angular/core';
1010

11-
import {MapMarker, MapMarkerModule} from './map-marker/index';
1211
import {GoogleMap, GoogleMapModule} from './google-map/index';
12+
import {MapInfoWindow, MapInfoWindowModule} from './map-info-window/index';
13+
import {MapMarker, MapMarkerModule} from './map-marker/index';
1314

1415
@NgModule({
1516
imports: [
1617
GoogleMapModule,
18+
MapInfoWindowModule,
1719
MapMarkerModule,
1820
],
1921
exports: [
2022
GoogleMap,
23+
MapInfoWindow,
2124
MapMarker,
2225
],
2326
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
export * from './map-info-window';
10+
export * from './map-info-window-module';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
import {NgModule} from '@angular/core';
10+
import {MapInfoWindow} from './map-info-window';
11+
12+
@NgModule({
13+
exports: [MapInfoWindow],
14+
declarations: [MapInfoWindow],
15+
})
16+
export class MapInfoWindowModule {
17+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import {Component} from '@angular/core';
2+
import {async, TestBed} from '@angular/core/testing';
3+
import {By} from '@angular/platform-browser';
4+
5+
import {DEFAULT_OPTIONS, GoogleMapModule, UpdatedGoogleMap} from '../google-map/index';
6+
import {MapMarker} from '../map-marker/index';
7+
import {
8+
createInfoWindowConstructorSpy,
9+
createInfoWindowSpy,
10+
createMapConstructorSpy,
11+
createMapSpy,
12+
TestingWindow
13+
} from '../testing/fake-google-map-utils';
14+
15+
import {MapInfoWindow, MapInfoWindowModule} from './index';
16+
17+
describe('MapInfoWindow', () => {
18+
let mapSpy: jasmine.SpyObj<UpdatedGoogleMap>;
19+
20+
beforeEach(async(() => {
21+
TestBed.configureTestingModule({
22+
imports: [
23+
GoogleMapModule,
24+
MapInfoWindowModule,
25+
],
26+
declarations: [TestApp],
27+
});
28+
}));
29+
30+
beforeEach(() => {
31+
TestBed.compileComponents();
32+
33+
mapSpy = createMapSpy(DEFAULT_OPTIONS);
34+
createMapConstructorSpy(mapSpy).and.callThrough();
35+
});
36+
37+
afterEach(() => {
38+
const testingWindow: TestingWindow = window;
39+
delete testingWindow.google;
40+
});
41+
42+
it('initializes a Google Map Info Window', () => {
43+
const infoWindowSpy = createInfoWindowSpy({});
44+
const infoWindowConstructorSpy =
45+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
46+
47+
const fixture = TestBed.createComponent(TestApp);
48+
fixture.detectChanges();
49+
50+
expect(infoWindowConstructorSpy).toHaveBeenCalledWith({
51+
position: undefined,
52+
content: jasmine.any(Node),
53+
});
54+
});
55+
56+
it('sets position', () => {
57+
const position: google.maps.LatLngLiteral = {lat: 5, lng: 7};
58+
const infoWindowSpy = createInfoWindowSpy({position});
59+
const infoWindowConstructorSpy =
60+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
61+
62+
const fixture = TestBed.createComponent(TestApp);
63+
fixture.componentInstance.position = position;
64+
fixture.detectChanges();
65+
66+
expect(infoWindowConstructorSpy).toHaveBeenCalledWith({
67+
position,
68+
content: jasmine.any(Node),
69+
});
70+
});
71+
72+
it('sets options', () => {
73+
const options: google.maps.InfoWindowOptions = {
74+
position: {lat: 3, lng: 5},
75+
maxWidth: 50,
76+
disableAutoPan: true,
77+
};
78+
const infoWindowSpy = createInfoWindowSpy(options);
79+
const infoWindowConstructorSpy =
80+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
81+
82+
const fixture = TestBed.createComponent(TestApp);
83+
fixture.componentInstance.options = options;
84+
fixture.detectChanges();
85+
86+
expect(infoWindowConstructorSpy).toHaveBeenCalledWith({
87+
...options,
88+
content: jasmine.any(Node),
89+
});
90+
});
91+
92+
it('gives preference to position over options', () => {
93+
const position: google.maps.LatLngLiteral = {lat: 5, lng: 7};
94+
const options: google.maps.InfoWindowOptions = {
95+
position: {lat: 3, lng: 5},
96+
maxWidth: 50,
97+
disableAutoPan: true,
98+
};
99+
const infoWindowSpy = createInfoWindowSpy({...options, position});
100+
const infoWindowConstructorSpy =
101+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
102+
103+
const fixture = TestBed.createComponent(TestApp);
104+
fixture.componentInstance.options = options;
105+
fixture.componentInstance.position = position;
106+
fixture.detectChanges();
107+
108+
expect(infoWindowConstructorSpy).toHaveBeenCalledWith({
109+
...options,
110+
position,
111+
content: jasmine.any(Node),
112+
});
113+
});
114+
115+
it('exposes methods that change the configuration of the info window', () => {
116+
const fakeMarker = {} as unknown as google.maps.Marker;
117+
const fakeMarkerComponent = {_marker: fakeMarker} as unknown as MapMarker;
118+
const infoWindowSpy = createInfoWindowSpy({});
119+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
120+
121+
const fixture = TestBed.createComponent(TestApp);
122+
const infoWindowComponent = fixture.debugElement.query(By.directive(
123+
MapInfoWindow))!.injector.get<MapInfoWindow>(MapInfoWindow);
124+
fixture.detectChanges();
125+
126+
infoWindowComponent.close();
127+
expect(infoWindowSpy.close).toHaveBeenCalled();
128+
129+
infoWindowComponent.open(fakeMarkerComponent);
130+
expect(infoWindowSpy.open).toHaveBeenCalledWith(mapSpy, fakeMarker);
131+
});
132+
133+
it('exposes methods that provide information about the info window', () => {
134+
const infoWindowSpy = createInfoWindowSpy({});
135+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
136+
137+
const fixture = TestBed.createComponent(TestApp);
138+
const infoWindowComponent = fixture.debugElement.query(By.directive(
139+
MapInfoWindow))!.injector.get<MapInfoWindow>(MapInfoWindow);
140+
fixture.detectChanges();
141+
142+
infoWindowSpy.getContent.and.returnValue('test content');
143+
expect(infoWindowComponent.getContent()).toBe('test content');
144+
145+
infoWindowComponent.getPosition();
146+
expect(infoWindowSpy.getPosition).toHaveBeenCalled();
147+
148+
infoWindowSpy.getZIndex.and.returnValue(5);
149+
expect(infoWindowComponent.getZIndex()).toBe(5);
150+
});
151+
152+
it('initializes info window event handlers', () => {
153+
const infoWindowSpy = createInfoWindowSpy({});
154+
createInfoWindowConstructorSpy(infoWindowSpy).and.callThrough();
155+
156+
const fixture = TestBed.createComponent(TestApp);
157+
fixture.detectChanges();
158+
159+
expect(infoWindowSpy.addListener).toHaveBeenCalledWith('closeclick', jasmine.any(Function));
160+
expect(infoWindowSpy.addListener)
161+
.not.toHaveBeenCalledWith('content_changed', jasmine.any(Function));
162+
expect(infoWindowSpy.addListener).not.toHaveBeenCalledWith('domready', jasmine.any(Function));
163+
expect(infoWindowSpy.addListener)
164+
.not.toHaveBeenCalledWith('position_changed', jasmine.any(Function));
165+
expect(infoWindowSpy.addListener)
166+
.not.toHaveBeenCalledWith('zindex_changed', jasmine.any(Function));
167+
});
168+
});
169+
170+
@Component({
171+
selector: 'test-app',
172+
template: `<google-map>
173+
<map-info-window [position]="position"
174+
[options]="options"
175+
(closeclick)="handleClose()">
176+
test content
177+
</map-info-window>
178+
</google-map>`,
179+
})
180+
class TestApp {
181+
position?: google.maps.LatLngLiteral;
182+
options?: google.maps.InfoWindowOptions;
183+
184+
handleClose() {}
185+
}

0 commit comments

Comments
 (0)