Skip to content

Commit 72321c8

Browse files
mbehrlichjelbourn
authored andcommitted
Maps rectangle (#18184)
Add rectangle component to @angular/google-maps that allows rectangles to be overlayed over the Google map.
1 parent c35144f commit 72321c8

File tree

8 files changed

+493
-2
lines changed

8 files changed

+493
-2
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<map-info-window>Testing 1 2 3</map-info-window>
1616
<map-polyline *ngIf="isPolylineDisplayed" [options]="polylineOptions"></map-polyline>
1717
<map-polygon *ngIf="isPolygonDisplayed" [options]="polygonOptions"></map-polygon>
18+
<map-rectangle *ngIf="isRectangleDisplayed" [options]="rectangleOptions"></map-rectangle>
1819
</google-map>
1920

2021
<p><label>Latitude:</label> {{display?.lat}}</p>
@@ -50,4 +51,19 @@
5051
</label>
5152
</div>
5253

54+
<div>
55+
<label for="rectangle-checkbox">
56+
Toggle Rectangle
57+
<input type="checkbox" (click)="toggleRectangleDisplay()">
58+
</label>
59+
</div>
60+
<div>
61+
<label for="editable-rectangle-checkbox">
62+
Toggle Editable Rectangle
63+
<input type="checkbox"
64+
[disabled]="!isRectangleDisplayed"
65+
(click)="toggleEditableRectangle()">
66+
</label>
67+
</div>
68+
5369
</div>

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,27 @@
77
*/
88

99
import {Component, ViewChild} from '@angular/core';
10-
import {MapInfoWindow, MapMarker, MapPolygon, MapPolyline} from '@angular/google-maps';
10+
import {
11+
MapInfoWindow,
12+
MapMarker,
13+
MapPolygon,
14+
MapPolyline,
15+
MapRectangle
16+
} from '@angular/google-maps';
1117

1218
const POLYLINE_PATH: google.maps.LatLngLiteral[] =
1319
[{lat: 25, lng: 26}, {lat: 26, lng: 27}, {lat: 30, lng: 34}];
1420

1521
const POLYGON_PATH: google.maps.LatLngLiteral[] =
1622
[{lat: 20, lng: 21}, {lat: 22, lng: 23}, {lat: 24, lng: 25}];
1723

24+
const RECTANGLE_BOUNDS: google.maps.LatLngBoundsLiteral = {
25+
east: 30,
26+
north: 15,
27+
west: 10,
28+
south: -5
29+
};
30+
1831
/** Demo Component for @angular/google-maps/map */
1932
@Component({
2033
selector: 'google-map-demo',
@@ -25,6 +38,7 @@ export class GoogleMapDemo {
2538
@ViewChild(MapInfoWindow) infoWindow: MapInfoWindow;
2639
@ViewChild(MapPolyline) polyline: MapPolyline;
2740
@ViewChild(MapPolygon) polygon: MapPolygon;
41+
@ViewChild(MapRectangle) rectangle: MapRectangle;
2842

2943
center = {lat: 24, lng: 12};
3044
markerOptions = {draggable: false};
@@ -37,6 +51,9 @@ export class GoogleMapDemo {
3751
isPolygonDisplayed = false;
3852
polygonOptions:
3953
google.maps.PolygonOptions = {paths: POLYGON_PATH, strokeColor: 'grey', strokeOpacity: 0.8};
54+
isRectangleDisplayed = false;
55+
rectangleOptions: google.maps
56+
.RectangleOptions = {bounds: RECTANGLE_BOUNDS, strokeColor: 'grey', strokeOpacity: 0.8};
4057

4158
handleClick(event: google.maps.MouseEvent) {
4259
this.markerPositions.push(event.latLng.toJSON());
@@ -77,4 +94,16 @@ export class GoogleMapDemo {
7794
paths: this.polygon.getPaths()
7895
};
7996
}
97+
98+
toggleRectangleDisplay() {
99+
this.isRectangleDisplayed = !this.isRectangleDisplayed;
100+
}
101+
102+
toggleEditableRectangle() {
103+
this.rectangleOptions = {
104+
...this.rectangleOptions,
105+
editable: !this.rectangleOptions.editable,
106+
bounds: this.rectangle.getBounds()
107+
};
108+
}
80109
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import {MapInfoWindow} from './map-info-window/map-info-window';
1313
import {MapMarker} from './map-marker/map-marker';
1414
import {MapPolygon} from './map-polygon/map-polygon';
1515
import {MapPolyline} from './map-polyline/map-polyline';
16+
import {MapRectangle} from './map-rectangle/map-rectangle';
1617

1718
const COMPONENTS = [
1819
GoogleMap,
1920
MapInfoWindow,
2021
MapMarker,
2122
MapPolyline,
2223
MapPolygon,
24+
MapRectangle,
2325
];
2426

2527
@NgModule({
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import {Component, ViewChild} from '@angular/core';
2+
import {async, TestBed} from '@angular/core/testing';
3+
import {By} from '@angular/platform-browser';
4+
5+
import {DEFAULT_OPTIONS, UpdatedGoogleMap} from '../google-map/google-map';
6+
import {GoogleMapsModule} from '../google-maps-module';
7+
import {
8+
createMapConstructorSpy,
9+
createMapSpy,
10+
createRectangleConstructorSpy,
11+
createRectangleSpy,
12+
TestingWindow,
13+
} from '../testing/fake-google-map-utils';
14+
15+
import {MapRectangle} from './map-rectangle';
16+
17+
describe('MapRectangle', () => {
18+
let mapSpy: jasmine.SpyObj<UpdatedGoogleMap>;
19+
let rectangleBounds: google.maps.LatLngBoundsLiteral;
20+
let rectangleOptions: google.maps.RectangleOptions;
21+
22+
beforeEach(async(() => {
23+
rectangleBounds = {east: 30, north: 15, west: 10, south: -5};
24+
rectangleOptions = {bounds: rectangleBounds, strokeColor: 'grey', strokeOpacity: 0.8};
25+
TestBed.configureTestingModule({
26+
imports: [GoogleMapsModule],
27+
declarations: [TestApp],
28+
});
29+
}));
30+
31+
beforeEach(() => {
32+
TestBed.compileComponents();
33+
34+
mapSpy = createMapSpy(DEFAULT_OPTIONS);
35+
createMapConstructorSpy(mapSpy).and.callThrough();
36+
});
37+
38+
afterEach(() => {
39+
const testingWindow: TestingWindow = window;
40+
delete testingWindow.google;
41+
});
42+
43+
it('initializes a Google Map Rectangle', () => {
44+
const rectangleSpy = createRectangleSpy({});
45+
const rectangleConstructorSpy = createRectangleConstructorSpy(rectangleSpy).and.callThrough();
46+
47+
const fixture = TestBed.createComponent(TestApp);
48+
fixture.detectChanges();
49+
50+
expect(rectangleConstructorSpy).toHaveBeenCalledWith({bounds: undefined});
51+
expect(rectangleSpy.setMap).toHaveBeenCalledWith(mapSpy);
52+
});
53+
54+
it('sets bounds from input', () => {
55+
const bounds: google.maps.LatLngBoundsLiteral = {east: 3, north: 5, west: -3, south: -5};
56+
const options: google.maps.RectangleOptions = {bounds};
57+
const rectangleSpy = createRectangleSpy(options);
58+
const rectangleConstructorSpy = createRectangleConstructorSpy(rectangleSpy).and.callThrough();
59+
60+
const fixture = TestBed.createComponent(TestApp);
61+
fixture.componentInstance.bounds = bounds;
62+
fixture.detectChanges();
63+
64+
expect(rectangleConstructorSpy).toHaveBeenCalledWith(options);
65+
});
66+
67+
it('gives precedence to bounds input over options', () => {
68+
const bounds: google.maps.LatLngBoundsLiteral = {east: 3, north: 5, west: -3, south: -5};
69+
const expectedOptions: google.maps.RectangleOptions = {...rectangleOptions, bounds};
70+
const rectangleSpy = createRectangleSpy(expectedOptions);
71+
const rectangleConstructorSpy = createRectangleConstructorSpy(rectangleSpy).and.callThrough();
72+
73+
const fixture = TestBed.createComponent(TestApp);
74+
fixture.componentInstance.options = rectangleOptions;
75+
fixture.componentInstance.bounds = bounds;
76+
fixture.detectChanges();
77+
78+
expect(rectangleConstructorSpy).toHaveBeenCalledWith(expectedOptions);
79+
});
80+
81+
it('exposes methods that provide information about the Rectangle', () => {
82+
const rectangleSpy = createRectangleSpy(rectangleOptions);
83+
createRectangleConstructorSpy(rectangleSpy).and.callThrough();
84+
85+
const fixture = TestBed.createComponent(TestApp);
86+
const rectangleComponent = fixture.debugElement.query(By.directive(
87+
MapRectangle))!.injector.get<MapRectangle>(MapRectangle);
88+
fixture.detectChanges();
89+
90+
rectangleComponent.getBounds();
91+
expect(rectangleSpy.getBounds).toHaveBeenCalled();
92+
93+
rectangleSpy.getDraggable.and.returnValue(true);
94+
expect(rectangleComponent.getDraggable()).toBe(true);
95+
96+
rectangleSpy.getEditable.and.returnValue(true);
97+
expect(rectangleComponent.getEditable()).toBe(true);
98+
99+
rectangleSpy.getVisible.and.returnValue(true);
100+
expect(rectangleComponent.getVisible()).toBe(true);
101+
});
102+
103+
it('initializes Rectangle event handlers', () => {
104+
const rectangleSpy = createRectangleSpy(rectangleOptions);
105+
createRectangleConstructorSpy(rectangleSpy).and.callThrough();
106+
107+
const addSpy = rectangleSpy.addListener;
108+
const fixture = TestBed.createComponent(TestApp);
109+
fixture.detectChanges();
110+
111+
expect(addSpy).toHaveBeenCalledWith('bounds_changed', jasmine.any(Function));
112+
expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function));
113+
expect(addSpy).not.toHaveBeenCalledWith('dblclick', jasmine.any(Function));
114+
expect(addSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function));
115+
expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function));
116+
expect(addSpy).not.toHaveBeenCalledWith('dragstart', jasmine.any(Function));
117+
expect(addSpy).not.toHaveBeenCalledWith('mousedown', jasmine.any(Function));
118+
expect(addSpy).not.toHaveBeenCalledWith('mousemove', jasmine.any(Function));
119+
expect(addSpy).not.toHaveBeenCalledWith('mouseout', jasmine.any(Function));
120+
expect(addSpy).not.toHaveBeenCalledWith('mouseover', jasmine.any(Function));
121+
expect(addSpy).not.toHaveBeenCalledWith('mouseup', jasmine.any(Function));
122+
expect(addSpy).toHaveBeenCalledWith('rightclick', jasmine.any(Function));
123+
});
124+
125+
it('should be able to add an event listener after init', () => {
126+
const rectangleSpy = createRectangleSpy(rectangleOptions);
127+
createRectangleConstructorSpy(rectangleSpy).and.callThrough();
128+
129+
const addSpy = rectangleSpy.addListener;
130+
const fixture = TestBed.createComponent(TestApp);
131+
fixture.detectChanges();
132+
133+
expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function));
134+
135+
// Pick an event that isn't bound in the template.
136+
const subscription = fixture.componentInstance.rectangle.rectangleDragend.subscribe();
137+
fixture.detectChanges();
138+
139+
expect(addSpy).toHaveBeenCalledWith('dragend', jasmine.any(Function));
140+
subscription.unsubscribe();
141+
});
142+
});
143+
144+
@Component({
145+
selector: 'test-app',
146+
template: `<google-map>
147+
<map-rectangle [options]="options"
148+
[bounds]="bounds"
149+
(boundsChanged)="handleBoundsChange()"
150+
(rectangleClick)="handleClick()"
151+
(rectangleRightclick)="handleRightclick()">
152+
</map-rectangle>
153+
</google-map>`,
154+
})
155+
class TestApp {
156+
@ViewChild(MapRectangle) rectangle: MapRectangle;
157+
options?: google.maps.RectangleOptions;
158+
bounds?: google.maps.LatLngBoundsLiteral;
159+
160+
handleBoundsChange() {}
161+
162+
handleClick() {}
163+
164+
handleRightclick() {}
165+
}

0 commit comments

Comments
 (0)