diff --git a/src/angular2_google_maps.ts b/src/angular2_google_maps.ts new file mode 100644 index 000000000..d68f65552 --- /dev/null +++ b/src/angular2_google_maps.ts @@ -0,0 +1,2 @@ +// main module +export * from './components'; diff --git a/src/components.ts b/src/components.ts new file mode 100644 index 000000000..e800cdef1 --- /dev/null +++ b/src/components.ts @@ -0,0 +1 @@ +export * from './components/google_map'; diff --git a/src/components/google_map.ts b/src/components/google_map.ts new file mode 100644 index 000000000..f303f9c43 --- /dev/null +++ b/src/components/google_map.ts @@ -0,0 +1,79 @@ +import {Component, Directive, Input, ContentChild, ViewEncapsulation, ElementRef, ViewChild, SimpleChange, NgZone} from 'angular2/angular2'; +import {GoogleMapsAPIWrapper} from '../services/google_maps_api_wrapper'; + +/** + * Container directive to create the Google Maps instance on a child element. + */ +@Directive({ + selector: '[sebm-google-map-container]', +}) +class SebmGoogleMapContainer { + constructor(private _el: ElementRef) {} + + getNativeElement(): HTMLElement { + return this._el.nativeElement; + } +} +/** + * Todo: add docs + */ +@Component({ + selector: 'sebm-google-map', + directives: [SebmGoogleMapContainer], + providers: [GoogleMapsAPIWrapper], + styles: [` + sebm-google-map-container { + width: 100%; + display: block; + } + `], + template: ` +
+ + ` +}) +export class SebmGoogleMap { + @ViewChild(SebmGoogleMapContainer) private _container: SebmGoogleMapContainer; + @Input() longitude: number = 0; + @Input() latitude: number = 0; + @Input() zoom: number = 8; + + constructor(private _zone: NgZone, private _mapsWrapper: GoogleMapsAPIWrapper) {} + + afterViewInit() { + this._initMapInstance(this._container.getNativeElement()); + } + + onChanges(changes: {[key:string]: SimpleChange}) { + if (!this._mapsWrapper.isInitialized()) { + return; + } + this._updateLatLng(changes); + } + + private _updateLatLng(changes: {[key:string]: SimpleChange}) { + if (changes['latitude'] || changes['longitude']) { + this._mapsWrapper.panTo({ + lat: this.latitude, + lng: this.longitude, + }); + } + } + + private _initMapInstance(el: HTMLElement) { + this._mapsWrapper.initialize(el, this.latitude, this.longitude, this.zoom); + this._handleMapsCenterChanged(); + this._handleZoomChanged(); + } + + private _handleMapsCenterChanged() { + this._mapsWrapper.getCenterChangeObservable().subscribe((latLng: google.maps.LatLngOptions) => { + this.latitude = latLng.lat; + this.longitude = latLng.lng; + }); + } + + private _handleZoomChanged() { + this._mapsWrapper.getZoomChangeObserable().subscribe((zoom: number) => this.zoom = zoom); + } +} diff --git a/src/custom_typings/google_maps.d.ts b/src/custom_typings/google_maps.d.ts new file mode 100644 index 000000000..1965da521 --- /dev/null +++ b/src/custom_typings/google_maps.d.ts @@ -0,0 +1,26 @@ +declare module google.maps { + export class Map { + constructor (el: HTMLElement, opts?: MapOptions); + panTo(latLng: LatLng|LatLngOptions): void; + setZoom(zoom: number): void; + addListener(eventName: string, fn: Function): void; + getCenter(): LatLng; + getZoom(): number; + } + + export class LatLng { + constructor(lat: number, lng: number); + lat(): number; + lng(): number; + } + + export interface LatLngOptions { + lat: number; + lng: number; + } + + export interface MapOptions { + center: LatLng|LatLngOptions; + zoom: number; + } +} diff --git a/src/custom_typings/typings.d.ts b/src/custom_typings/typings.d.ts new file mode 100644 index 000000000..be9ce8948 --- /dev/null +++ b/src/custom_typings/typings.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/services/google_maps_api_wrapper.ts b/src/services/google_maps_api_wrapper.ts new file mode 100644 index 000000000..d96bf584f --- /dev/null +++ b/src/services/google_maps_api_wrapper.ts @@ -0,0 +1,77 @@ +import {Injectable, Inject, NgZone} from 'angular2/angular2'; +import {Observable} from 'rx'; + +/** + * Wrapper class that handles the communication with the Google Maps Javascript API v3 + */ +@Injectable() +export class GoogleMapsAPIWrapper { + private _el: HTMLElement; + private _map: google.maps.Map; + private _isInitialized: boolean; + + private _centerChangeObservable: Observable; + private _zoomChangeObservable: Observable; + + constructor(private _zone: NgZone) { + } + + createEventObservable(eventName: string, callback: (observer: Rx.Observer) => void): Observable { + return Observable.create((observer: Rx.Observer) => { + this._map.addListener(eventName, () => { + this._zone.run(() => { + callback(observer); + }); + }); + }); + } + + initialize(_el: HTMLElement, latitude: number, longitude: number, zoom: number) { + if (this._isInitialized) { + throw new Error('GooelMapsAPIWrapper is already initialized!'); + } + this._isInitialized = true; + this._el = _el; + this._map = new google.maps.Map(this._el, { + center: { + lat: latitude, + lng: longitude + }, + zoom: zoom + }); + this._createObservables(); + } + + private _createObservables() { + this._centerChangeObservable = this.createEventObservable('center_changed', (observer: Rx.Observer) => { + const center = this._map.getCenter(); + observer.onNext({ + lat: center.lat(), + lng: center.lng() + }); + }); + this._zoomChangeObservable = this.createEventObservable('zoom_changed', (observer: Rx.Observer) => { + observer.onNext(this._map.getZoom()); + }); + } + + getZoomChangeObserable(): Observable { + return this._zoomChangeObservable; + } + + getCenterChangeObservable(): Observable { + return this._centerChangeObservable; + } + + panTo(latLng: google.maps.LatLngOptions) { + this._map.panTo(latLng); + } + + getCenter(): google.maps.LatLng { + return this._map.getCenter(); + } + + isInitialized(): boolean { + return this._isInitialized; + } +}