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;
+ }
+}