From 2e4878b86b718eef02d0a02f1a1bfabc67af3184 Mon Sep 17 00:00:00 2001 From: Sebastian Holstein Date: Wed, 4 Jul 2018 21:46:34 +0200 Subject: [PATCH] feat(core): add rectangle support (#1450) add rectangle functionality closes #570 --- packages/core/core.module.ts | 3 +- packages/core/directives.ts | 1 + packages/core/directives/map.ts | 5 +- packages/core/directives/rectangle.ts | 296 ++++++++++++++++ packages/core/services.ts | 1 + .../core/services/google-maps-api-wrapper.ts | 10 + packages/core/services/google-maps-types.ts | 30 ++ .../managers/rectangle-manager.spec.ts | 325 ++++++++++++++++++ .../services/managers/rectangle-manager.ts | 100 ++++++ 9 files changed, 768 insertions(+), 3 deletions(-) create mode 100644 packages/core/directives/rectangle.ts create mode 100644 packages/core/services/managers/rectangle-manager.spec.ts create mode 100644 packages/core/services/managers/rectangle-manager.ts diff --git a/packages/core/core.module.ts b/packages/core/core.module.ts index 98ec6a37f..f4f4277bc 100644 --- a/packages/core/core.module.ts +++ b/packages/core/core.module.ts @@ -1,6 +1,7 @@ import {ModuleWithProviders, NgModule} from '@angular/core'; import {AgmMap} from './directives/map'; import {AgmCircle} from './directives/circle'; +import {AgmRectangle} from './directives/rectangle'; import {AgmInfoWindow} from './directives/info-window'; import {AgmMarker} from './directives/marker'; import {AgmPolygon} from './directives/polygon'; @@ -18,7 +19,7 @@ import {BROWSER_GLOBALS_PROVIDERS} from './utils/browser-globals'; */ export function coreDirectives() { return [ - AgmMap, AgmMarker, AgmInfoWindow, AgmCircle, + AgmMap, AgmMarker, AgmInfoWindow, AgmCircle, AgmRectangle, AgmPolygon, AgmPolyline, AgmPolylinePoint, AgmKmlLayer, AgmDataLayer ]; diff --git a/packages/core/directives.ts b/packages/core/directives.ts index cf95e9436..d4bb07238 100644 --- a/packages/core/directives.ts +++ b/packages/core/directives.ts @@ -1,5 +1,6 @@ export {AgmMap} from './directives/map'; export {AgmCircle} from './directives/circle'; +export {AgmRectangle} from './directives/rectangle'; export {AgmInfoWindow} from './directives/info-window'; export {AgmKmlLayer} from './directives/kml-layer'; export {AgmDataLayer} from './directives/data-layer'; diff --git a/packages/core/directives/map.ts b/packages/core/directives/map.ts index 06bd559ad..00d30964f 100644 --- a/packages/core/directives/map.ts +++ b/packages/core/directives/map.ts @@ -8,6 +8,7 @@ import { RotateControlOptions, ScaleControlOptions, StreetViewControlOptions, ZoomControlOptions} from '../services/google-maps-types'; import {LatLngBounds, LatLngBoundsLiteral, MapTypeStyle} from '../services/google-maps-types'; import {CircleManager} from '../services/managers/circle-manager'; +import {RectangleManager} from '../services/managers/rectangle-manager'; import {InfoWindowManager} from '../services/managers/info-window-manager'; import {MarkerManager} from '../services/managers/marker-manager'; import {PolygonManager} from '../services/managers/polygon-manager'; @@ -41,8 +42,8 @@ import {DataLayerManager} from './../services/managers/data-layer-manager'; @Component({ selector: 'agm-map', providers: [ - GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, PolylineManager, - PolygonManager, KmlLayerManager, DataLayerManager + GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, RectangleManager, + PolylineManager, PolygonManager, KmlLayerManager, DataLayerManager ], host: { // todo: deprecated - we will remove it with the next version diff --git a/packages/core/directives/rectangle.ts b/packages/core/directives/rectangle.ts new file mode 100644 index 000000000..20957c180 --- /dev/null +++ b/packages/core/directives/rectangle.ts @@ -0,0 +1,296 @@ +import { + Directive, + EventEmitter, + OnChanges, + OnDestroy, + OnInit, + SimpleChange, + Input, + Output +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { MouseEvent } from '../map-types'; +import { + LatLngBounds, + LatLngBoundsLiteral +} from '../services/google-maps-types'; +import { MouseEvent as MapMouseEvent } from '../services/google-maps-types'; +import { RectangleManager } from '../services/managers/rectangle-manager'; + +@Directive({ + selector: 'agm-rectangle' +}) +export class AgmRectangle implements OnInit, OnChanges, OnDestroy { + /** + * The north position of the rectangle (required). + */ + @Input() north: number; + + /** + * The east position of the rectangle (required). + */ + @Input() east: number; + + /** + * The south position of the rectangle (required). + */ + @Input() south: number; + + /** + * The west position of the rectangle (required). + */ + @Input() west: number; + + /** + * Indicates whether this Rectangle handles mouse events. Defaults to true. + */ + @Input() clickable: boolean = true; + + /** + * If set to true, the user can drag this rectangle over the map. Defaults to false. + */ + // tslint:disable-next-line:no-input-rename + @Input('rectangleDraggable') draggable: boolean = false; + + /** + * If set to true, the user can edit this rectangle by dragging the control points shown at + * the center and around the circumference of the rectangle. Defaults to false. + */ + @Input() editable: boolean = false; + + /** + * The fill color. All CSS3 colors are supported except for extended named colors. + */ + @Input() fillColor: string; + + /** + * The fill opacity between 0.0 and 1.0. + */ + @Input() fillOpacity: number; + + /** + * The stroke color. All CSS3 colors are supported except for extended named colors. + */ + @Input() strokeColor: string; + + /** + * The stroke opacity between 0.0 and 1.0 + */ + @Input() strokeOpacity: number; + + /** + * The stroke position. Defaults to CENTER. + * This property is not supported on Internet Explorer 8 and earlier. + */ + @Input() strokePosition: 'CENTER' | 'INSIDE' | 'OUTSIDE' = 'CENTER'; + + /** + * The stroke width in pixels. + */ + @Input() strokeWeight: number = 0; + + /** + * Whether this rectangle is visible on the map. Defaults to true. + */ + @Input() visible: boolean = true; + + /** + * The zIndex compared to other polys. + */ + @Input() zIndex: number; + + /** + * This event is fired when the rectangle's is changed. + */ + @Output() + boundsChange: EventEmitter = new EventEmitter< + LatLngBoundsLiteral + >(); + + /** + * This event emitter gets emitted when the user clicks on the rectangle. + */ + @Output() + rectangleClick: EventEmitter = new EventEmitter(); + + /** + * This event emitter gets emitted when the user clicks on the rectangle. + */ + @Output() + rectangleDblClick: EventEmitter = new EventEmitter(); + + /** + * This event is repeatedly fired while the user drags the rectangle. + */ + @Output() drag: EventEmitter = new EventEmitter(); + + /** + * This event is fired when the user stops dragging the rectangle. + */ + @Output() dragEnd: EventEmitter = new EventEmitter(); + + /** + * This event is fired when the user starts dragging the rectangle. + */ + @Output() + dragStart: EventEmitter = new EventEmitter(); + + /** + * This event is fired when the DOM mousedown event is fired on the rectangle. + */ + @Output() + mouseDown: EventEmitter = new EventEmitter(); + + /** + * This event is fired when the DOM mousemove event is fired on the rectangle. + */ + @Output() + mouseMove: EventEmitter = new EventEmitter(); + + /** + * This event is fired on rectangle mouseout. + */ + @Output() mouseOut: EventEmitter = new EventEmitter(); + + /** + * This event is fired on rectangle mouseover. + */ + @Output() + mouseOver: EventEmitter = new EventEmitter(); + + /** + * This event is fired when the DOM mouseup event is fired on the rectangle. + */ + @Output() mouseUp: EventEmitter = new EventEmitter(); + + /** + * This event is fired when the rectangle is right-clicked on. + */ + @Output() + rightClick: EventEmitter = new EventEmitter(); + + private _rectangleAddedToManager: boolean = false; + + private static _mapOptions: string[] = [ + 'fillColor', + 'fillOpacity', + 'strokeColor', + 'strokeOpacity', + 'strokePosition', + 'strokeWeight', + 'visible', + 'zIndex', + 'clickable' + ]; + + private _eventSubscriptions: Subscription[] = []; + + constructor(private _manager: RectangleManager) {} + + /** @internal */ + ngOnInit() { + this._manager.addRectangle(this); + this._rectangleAddedToManager = true; + this._registerEventListeners(); + } + + /** @internal */ + ngOnChanges(changes: { [key: string]: SimpleChange }) { + if (!this._rectangleAddedToManager) { + return; + } + if ( + changes['north'] || + changes['east'] || + changes['south'] || + changes['west'] + ) { + this._manager.setBounds(this); + } + if (changes['editable']) { + this._manager.setEditable(this); + } + if (changes['draggable']) { + this._manager.setDraggable(this); + } + if (changes['visible']) { + this._manager.setVisible(this); + } + this._updateRectangleOptionsChanges(changes); + } + + private _updateRectangleOptionsChanges(changes: { + [propName: string]: SimpleChange; + }) { + let options: { [propName: string]: any } = {}; + let optionKeys = Object.keys(changes).filter( + k => AgmRectangle._mapOptions.indexOf(k) !== -1 + ); + optionKeys.forEach(k => { + options[k] = changes[k].currentValue; + }); + if (optionKeys.length > 0) { + this._manager.setOptions(this, options); + } + } + + private _registerEventListeners() { + let events: Map> = new Map< + string, + EventEmitter + >(); + events.set('bounds_changed', this.boundsChange); + events.set('click', this.rectangleClick); + events.set('dblclick', this.rectangleDblClick); + events.set('drag', this.drag); + events.set('dragend', this.dragEnd); + events.set('dragStart', this.dragStart); + events.set('mousedown', this.mouseDown); + events.set('mousemove', this.mouseMove); + events.set('mouseout', this.mouseOut); + events.set('mouseover', this.mouseOver); + events.set('mouseup', this.mouseUp); + events.set('rightclick', this.rightClick); + + events.forEach((eventEmitter, eventName) => { + this._eventSubscriptions.push( + this._manager + .createEventObservable(eventName, this) + .subscribe(value => { + switch (eventName) { + case 'bounds_changed': + this._manager.getBounds(this).then(bounds => + eventEmitter.emit({ + north: bounds.getNorthEast().lat(), + east: bounds.getNorthEast().lng(), + south: bounds.getSouthWest().lat(), + west: bounds.getSouthWest().lng() + }) + ); + break; + default: + eventEmitter.emit({ + coords: { lat: value.latLng.lat(), lng: value.latLng.lng() } + }); + } + }) + ); + }); + } + + /** @internal */ + ngOnDestroy() { + this._eventSubscriptions.forEach(function(s: Subscription) { + s.unsubscribe(); + }); + this._eventSubscriptions = null; + this._manager.removeRectangle(this); + } + + /** + * Gets the LatLngBounds of this Rectangle. + */ + getBounds(): Promise { + return this._manager.getBounds(this); + } +} diff --git a/packages/core/services.ts b/packages/core/services.ts index a57b4240b..1dfb9206f 100644 --- a/packages/core/services.ts +++ b/packages/core/services.ts @@ -1,5 +1,6 @@ export {GoogleMapsAPIWrapper} from './services/google-maps-api-wrapper'; export {CircleManager} from './services/managers/circle-manager'; +export {RectangleManager} from './services/managers/rectangle-manager'; export {InfoWindowManager} from './services/managers/info-window-manager'; export {MarkerManager} from './services/managers/marker-manager'; export {PolygonManager} from './services/managers/polygon-manager'; diff --git a/packages/core/services/google-maps-api-wrapper.ts b/packages/core/services/google-maps-api-wrapper.ts index 1dfb2965e..2121cce8d 100644 --- a/packages/core/services/google-maps-api-wrapper.ts +++ b/packages/core/services/google-maps-api-wrapper.ts @@ -64,6 +64,16 @@ export class GoogleMapsAPIWrapper { }); } + /** + * Creates a google.map.Rectangle for the current map. + */ + createRectangle(options: mapTypes.RectangleOptions): Promise { + return this._map.then((map: mapTypes.GoogleMap) => { + options.map = map; + return new google.maps.Rectangle(options); + }); + } + createPolyline(options: PolylineOptions): Promise { return this.getNativeMap().then((map: mapTypes.GoogleMap) => { let line = new google.maps.Polyline(options); diff --git a/packages/core/services/google-maps-types.ts b/packages/core/services/google-maps-types.ts index fdd649b01..1012a1db5 100644 --- a/packages/core/services/google-maps-types.ts +++ b/packages/core/services/google-maps-types.ts @@ -94,6 +94,36 @@ export interface CircleOptions { zIndex?: number; } +export interface Rectangle extends MVCObject { + getBounds(): LatLngBounds; + getDraggable(): boolean; + getEditable(): boolean; + getMap(): GoogleMap; + getVisible(): boolean; + setBounds(bounds: LatLngBounds|LatLngBoundsLiteral): void; + setDraggable(draggable: boolean): void; + setEditable(editable: boolean): void; + setMap(map: GoogleMap): void; + setOptions(options: RectangleOptions): void; + setVisible(visible: boolean): void; +} + +export interface RectangleOptions { + bounds?: LatLngBounds|LatLngBoundsLiteral; + clickable?: boolean; + draggable?: boolean; + editable?: boolean; + fillColor?: string; + fillOpacity?: number; + map?: GoogleMap; + strokeColor?: string; + strokeOpacity?: number; + strokePosition?: 'CENTER'|'INSIDE'|'OUTSIDE'; + strokeWeight?: number; + visible?: boolean; + zIndex?: number; +} + export interface LatLngBounds { contains(latLng: LatLng): boolean; equals(other: LatLngBounds|LatLngBoundsLiteral): boolean; diff --git a/packages/core/services/managers/rectangle-manager.spec.ts b/packages/core/services/managers/rectangle-manager.spec.ts new file mode 100644 index 000000000..a91af54b6 --- /dev/null +++ b/packages/core/services/managers/rectangle-manager.spec.ts @@ -0,0 +1,325 @@ +import { NgZone } from '@angular/core'; +import { TestBed, async, inject } from '@angular/core/testing'; + +import { AgmRectangle } from './../../directives/rectangle'; +import { GoogleMapsAPIWrapper } from './../google-maps-api-wrapper'; +import { Rectangle } from './../google-maps-types'; +import { RectangleManager } from './../managers/rectangle-manager'; + +describe('RectangleManager', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { + provide: NgZone, + useFactory: () => new NgZone({ enableLongStackTrace: true }) + }, + RectangleManager, + { + provide: GoogleMapsAPIWrapper, + useValue: { createRectangle: jest.fn() } + } + ] + }); + }); + + describe('Create a new rectangle', () => { + it('should call the mapsApiWrapper when creating a new rectangle', inject( + [RectangleManager, GoogleMapsAPIWrapper], + ( + rectangleManager: RectangleManager, + apiWrapper: GoogleMapsAPIWrapper + ) => { + const newRectangle = new AgmRectangle(rectangleManager); + newRectangle.north = 12.7; + newRectangle.east = 56.6; + newRectangle.south = 89.2; + newRectangle.west = 52.6; + rectangleManager.addRectangle(newRectangle); + + expect(apiWrapper.createRectangle).toHaveBeenCalledWith({ + bounds: { + north: 12.7, + east: 56.6, + south: 89.2, + west: 52.6 + }, + clickable: true, + draggable: false, + editable: false, + fillColor: undefined, + fillOpacity: undefined, + strokeColor: undefined, + strokeOpacity: undefined, + strokePosition: 'CENTER', + strokeWeight: 0, + visible: true, + zIndex: undefined + }); + } + )); + }); + + describe('Delete a rectangle', () => { + it('should set the map to null when deleting a existing rectangle', inject( + [RectangleManager, GoogleMapsAPIWrapper], + ( + rectangleManager: RectangleManager, + apiWrapper: GoogleMapsAPIWrapper + ) => { + const newRectangle = new AgmRectangle(rectangleManager); + newRectangle.north = 12.7; + newRectangle.east = 56.6; + newRectangle.south = 89.2; + newRectangle.west = 52.6; + + const rectangleInstance: any = { + setMap: jest.fn() + }; + (apiWrapper.createRectangle).mockReturnValue( + Promise.resolve(rectangleInstance) + ); + + rectangleManager.addRectangle(newRectangle); + rectangleManager.removeRectangle(newRectangle).then(() => { + expect(rectangleInstance.setMap).toHaveBeenCalledWith(null); + }); + } + )); + }); + + describe('Set bounds option', () => { + it('should update that rectangle via setBounds method when the bounds changes', async( + inject( + [RectangleManager, GoogleMapsAPIWrapper], + ( + rectangleManager: RectangleManager, + apiWrapper: GoogleMapsAPIWrapper + ) => { + const newRectangle = new AgmRectangle(rectangleManager); + newRectangle.north = 12.7; + newRectangle.east = 56.6; + newRectangle.south = 89.2; + newRectangle.west = 52.6; + + const rectangleInstance: Rectangle = { + setMap: jest.fn(), + setBounds: jest.fn() + }; + (apiWrapper.createRectangle).mockReturnValue( + Promise.resolve(rectangleInstance) + ); + + rectangleManager.addRectangle(newRectangle); + expect(apiWrapper.createRectangle).toHaveBeenCalledWith({ + bounds: { + north: 12.7, + east: 56.6, + south: 89.2, + west: 52.6 + }, + clickable: true, + draggable: false, + editable: false, + fillColor: undefined, + fillOpacity: undefined, + strokeColor: undefined, + strokeOpacity: undefined, + strokePosition: 'CENTER', + strokeWeight: 0, + visible: true, + zIndex: undefined + }); + newRectangle.north = 15.6; + newRectangle.east = 45.2; + newRectangle.south = 12.6; + newRectangle.west = 41.3; + + const bounds = { + north: 15.6, + east: 45.2, + south: 12.6, + west: 41.3 + }; + + return rectangleManager.setBounds(newRectangle).then(() => { + expect(rectangleInstance.setBounds).toHaveBeenCalledWith(bounds); + }); + } + ) + )); + }); + + describe('Set fill/stroke opacity option', () => { + it('should update that rectangle via setOptions method when the options changes', async( + inject( + [RectangleManager, GoogleMapsAPIWrapper], + ( + rectangleManager: RectangleManager, + apiWrapper: GoogleMapsAPIWrapper + ) => { + const newRectangle = new AgmRectangle(rectangleManager); + newRectangle.north = 12.7; + newRectangle.east = 56.6; + newRectangle.south = 89.2; + newRectangle.west = 52.6; + newRectangle.fillOpacity = 0.4; + newRectangle.strokeOpacity = 0.4; + + const rectangleInstance: any = { + setMap: jest.fn(), + setOptions: jest.fn() + }; + + (apiWrapper.createRectangle).mockReturnValue( + Promise.resolve(rectangleInstance) + ); + + rectangleManager.addRectangle(newRectangle); + expect(apiWrapper.createRectangle).toHaveBeenCalledWith({ + bounds: { + north: 12.7, + east: 56.6, + south: 89.2, + west: 52.6 + }, + clickable: true, + draggable: false, + editable: false, + fillColor: undefined, + fillOpacity: 0.4, + strokeColor: undefined, + strokeOpacity: 0.4, + strokePosition: 'CENTER', + strokeWeight: 0, + visible: true, + zIndex: undefined + }); + newRectangle.fillOpacity = 0.6; + newRectangle.strokeOpacity = 0.6; + + const options = { + fillOpacity: 0.6, + strokeOpacity: 0.6 + }; + + return rectangleManager.setOptions(newRectangle, options).then(() => { + expect(rectangleInstance.setOptions).toHaveBeenCalledWith(options); + }); + } + ) + )); + }); + + describe('Set fill/stroke color option', () => { + it('should update that rectangle via setOptions method when the options changes', async( + inject( + [RectangleManager, GoogleMapsAPIWrapper], + ( + rectangleManager: RectangleManager, + apiWrapper: GoogleMapsAPIWrapper + ) => { + const newRectangle = new AgmRectangle(rectangleManager); + newRectangle.north = 12.7; + newRectangle.east = 56.6; + newRectangle.south = 89.2; + newRectangle.west = 52.6; + newRectangle.fillColor = '#FF7F50'; + newRectangle.strokeColor = '#FF7F50'; + + const rectangleInstance: any = { + setMap: jest.fn(), + setOptions: jest.fn() + }; + (apiWrapper.createRectangle).mockReturnValue( + Promise.resolve(rectangleInstance) + ); + + rectangleManager.addRectangle(newRectangle); + expect(apiWrapper.createRectangle).toHaveBeenCalledWith({ + bounds: { + north: 12.7, + east: 56.6, + south: 89.2, + west: 52.6 + }, + clickable: true, + draggable: false, + editable: false, + fillColor: '#FF7F50', + fillOpacity: undefined, + strokeColor: '#FF7F50', + strokeOpacity: undefined, + strokePosition: 'CENTER', + strokeWeight: 0, + visible: true, + zIndex: undefined + }); + newRectangle.fillColor = '#00008B'; + newRectangle.strokeColor = '#00008B'; + + const options = { + fillColor: '#00008B', + strokeColor: '#00008B' + }; + + return rectangleManager.setOptions(newRectangle, options).then(() => { + expect(rectangleInstance.setOptions).toHaveBeenCalledWith(options); + }); + } + ) + )); + }); + + describe('Set visible option', () => { + it('should update that rectangle via setVisible method when the visible changes', async( + inject( + [RectangleManager, GoogleMapsAPIWrapper], + ( + rectangleManager: RectangleManager, + apiWrapper: GoogleMapsAPIWrapper + ) => { + const newRectangle = new AgmRectangle(rectangleManager); + newRectangle.north = 12.7; + newRectangle.east = 56.6; + newRectangle.south = 89.2; + newRectangle.west = 52.6; + newRectangle.visible = false; + + const rectangleInstance: any = { + setMap: jest.fn(), + setVisible: jest.fn() + }; + (apiWrapper.createRectangle).mockReturnValue( + Promise.resolve(rectangleInstance) + ); + + rectangleManager.addRectangle(newRectangle); + expect(apiWrapper.createRectangle).toHaveBeenCalledWith({ + bounds: { + north: 12.7, + east: 56.6, + south: 89.2, + west: 52.6 + }, + clickable: true, + draggable: false, + editable: false, + fillColor: undefined, + fillOpacity: undefined, + strokeColor: undefined, + strokeOpacity: undefined, + strokePosition: 'CENTER', + strokeWeight: 0, + visible: false, + zIndex: undefined + }); + newRectangle.visible = true; + return rectangleManager.setVisible(newRectangle).then(() => { + expect(rectangleInstance.setVisible).toHaveBeenCalledWith(true); + }); + } + ) + )); + }); +}); diff --git a/packages/core/services/managers/rectangle-manager.ts b/packages/core/services/managers/rectangle-manager.ts new file mode 100644 index 000000000..152f58454 --- /dev/null +++ b/packages/core/services/managers/rectangle-manager.ts @@ -0,0 +1,100 @@ +import {Injectable, NgZone} from '@angular/core'; + +import {Observable} from 'rxjs'; +import {Observer} from 'rxjs'; + +import {AgmRectangle} from '../../directives/rectangle'; +import {GoogleMapsAPIWrapper} from '../google-maps-api-wrapper'; +import * as mapTypes from '../google-maps-types'; + +@Injectable() +export class RectangleManager { + private _rectangles: Map> = + new Map>(); + + constructor(private _apiWrapper: GoogleMapsAPIWrapper, private _zone: NgZone) {} + + addRectangle(rectangle: AgmRectangle) { + this._rectangles.set(rectangle, this._apiWrapper.createRectangle({ + bounds: { + north: rectangle.north, + east: rectangle.east, + south: rectangle.south, + west: rectangle.west + }, + clickable: rectangle.clickable, + draggable: rectangle.draggable, + editable: rectangle.editable, + fillColor: rectangle.fillColor, + fillOpacity: rectangle.fillOpacity, + strokeColor: rectangle.strokeColor, + strokeOpacity: rectangle.strokeOpacity, + strokePosition: rectangle.strokePosition, + strokeWeight: rectangle.strokeWeight, + visible: rectangle.visible, + zIndex: rectangle.zIndex + })); + } + + /** + * Removes the given rectangle from the map. + */ + removeRectangle(rectangle: AgmRectangle): Promise { + return this._rectangles.get(rectangle).then((r) => { + r.setMap(null); + this._rectangles.delete(rectangle); + }); + } + + setOptions(rectangle: AgmRectangle, options: mapTypes.RectangleOptions): Promise { + return this._rectangles.get(rectangle).then((r) => r.setOptions(options)); + } + + getBounds(rectangle: AgmRectangle): Promise { + return this._rectangles.get(rectangle).then((r) => r.getBounds()); + } + + setBounds(rectangle: AgmRectangle): Promise { + return this._rectangles.get(rectangle).then((r) => { + return r.setBounds({ + north: rectangle.north, + east: rectangle.east, + south: rectangle.south, + west: rectangle.west + }); + }); + } + + setEditable(rectangle: AgmRectangle): Promise { + return this._rectangles.get(rectangle).then((r) => { + return r.setEditable(rectangle.editable); + }); + } + + setDraggable(rectangle: AgmRectangle): Promise { + return this._rectangles.get(rectangle).then((r) => { + return r.setDraggable(rectangle.draggable); + }); + } + + setVisible(rectangle: AgmRectangle): Promise { + return this._rectangles.get(rectangle).then((r) => { + return r.setVisible(rectangle.visible); + }); + } + + createEventObservable(eventName: string, rectangle: AgmRectangle): Observable { + return Observable.create((observer: Observer) => { + let listener: mapTypes.MapsEventListener = null; + this._rectangles.get(rectangle).then((r) => { + listener = r.addListener(eventName, (e: T) => this._zone.run(() => observer.next(e))); + }); + + return () => { + if (listener !== null) { + listener.remove(); + } + }; + }); + } +}