Skip to content

Commit

Permalink
feat(components): add sebm-google-map component
Browse files Browse the repository at this point in the history
<seb-google-map> is a basic implementation of viewable google map.

It supports 3 bindings:
- latitude
- longitude
- zoom
  • Loading branch information
sebholstein committed Nov 5, 2015
1 parent 4de9971 commit 120a5df
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/angular2_google_maps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// main module
export * from './components';
1 change: 1 addition & 0 deletions src/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/google_map';
79 changes: 79 additions & 0 deletions src/components/google_map.ts
Original file line number Diff line number Diff line change
@@ -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: `
<div sebm-google-map-container class="sebm-google-map-container"></div>
<ng-content></ng-content>
`
})
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);
}
}
26 changes: 26 additions & 0 deletions src/custom_typings/google_maps.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions src/custom_typings/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference path="../../node_modules/rx/ts/rx.all.d.ts" />
77 changes: 77 additions & 0 deletions src/services/google_maps_api_wrapper.ts
Original file line number Diff line number Diff line change
@@ -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<google.maps.LatLngOptions>;
private _zoomChangeObservable: Observable<number>;

constructor(private _zone: NgZone) {
}

createEventObservable<E>(eventName: string, callback: (observer: Rx.Observer<E>) => void): Observable<E> {
return Observable.create((observer: Rx.Observer<E>) => {
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<google.maps.LatLngOptions>('center_changed', (observer: Rx.Observer<google.maps.LatLngOptions>) => {
const center = this._map.getCenter();
observer.onNext({
lat: center.lat(),
lng: center.lng()
});
});
this._zoomChangeObservable = this.createEventObservable<number>('zoom_changed', (observer: Rx.Observer<number>) => {
observer.onNext(this._map.getZoom());
});
}

getZoomChangeObserable(): Observable<number> {
return this._zoomChangeObservable;
}

getCenterChangeObservable(): Observable<google.maps.LatLngOptions> {
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;
}
}

0 comments on commit 120a5df

Please sign in to comment.