diff --git a/angular-weather.js b/angular-weather.js
new file mode 100644
index 0000000..3a2124f
--- /dev/null
+++ b/angular-weather.js
@@ -0,0 +1,195 @@
+'use strict';
+
+angular.module('angular-weather', [])
+ .constant('openweatherEndpoint', 'http://api.openweathermap.org/data/2.5/weather')
+ .constant('Config', {
+ openweather: {
+ apikey: ''
+ }
+ })
+ .directive('nwWeatherTemperature', function () {
+ return {
+ restrict: 'EA',
+ template: '
{{ctrlWeather.data.temperature | number:0 }}°
',
+ controller: function(nwWeather, nwWeatherIcons) {
+ var weather = this;
+
+ // Request the weather data.
+ nwWeather.get(this.account).then(function(response) {
+ weather.data = response;
+ weather.data.iconClass = nwWeatherIcons[response.icon];
+ });
+ },
+ controllerAs: 'ctrlWeather',
+ bindToController: true,
+ // Isolate scope.
+ scope: {
+ account: '='
+ }
+ };
+ })
+ .service('nwWeather', function ($q, $http, $timeout, $state, $rootScope, openweatherEndpoint, nwWeatherIcons, Config) {
+ var self = this;
+
+ // A private cache key.
+ var cache = {};
+
+ // Promise in progress of Weather.
+ var getWeather;
+
+ // Update event broadcast name.
+ var broadcastUpdateEventName = 'nwWeatherChanged';
+
+ /**
+ * Return the promise with the category list, from cache or the server.
+ *
+ * @param accountId - int
+ * The account ID.
+ * @param categoryId - int
+ * The category ID.
+ *
+ * @returns {Promise}
+ */
+ this.get = function(account) {
+ getWeather = $q.when(getWeather || angular.copy(cache.data) || getWeatherFromServer(account));
+
+ // Clear the promise cached, after resolve or reject the promise. Permit access to the cache data, when
+ // the promise excecution is done (finally).
+ getWeather.finally(function getWeatherFinally() {
+ getWeather = undefined;
+ });
+
+ return getWeather;
+ };
+
+ /**
+ * Return Weather array from the server.
+ *
+ * @returns {$q.promise}
+ */
+ function getWeatherFromServer(account) {
+ var deferred = $q.defer();
+ var url = openweatherEndpoint;
+ var params = {
+ lat: account.center.lat,
+ lon: account.center.lng,
+ units: 'metric',
+ APPID: Config.openweather.apikey || ''
+ };
+ $http({
+ method: 'GET',
+ url: url,
+ params: params,
+ withoutToken: true,
+ cache: true
+ }).success(function(data) {
+ // Prepare the Weather object.
+ var weatherData = prepareWeather(data);
+ setCache(weatherData);
+ deferred.resolve(weatherData);
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Save Weather in cache, and broadcast en event to inform that the Weather data changed.
+ *
+ * @param data
+ * Collection resulted from the request.
+ */
+ function setCache(data) {
+ // Cache Weather data.
+ cache = {
+ data: data,
+ timestamp: new Date()
+ };
+ // Clear cache in 10 minute.
+ $timeout(function() {
+ cache.data = undefined;
+ }, 600000);
+ $rootScope.$broadcast(broadcastUpdateEventName);
+ }
+
+ /**
+ * Prepare Weather object with order by list, tree and collection indexed by id.
+ *
+ * Return the Weather object into a promises.
+ *
+ * @param getWeather - {$q.promise)
+ * Promise of list of Weather, comming from cache or the server.
+ * @param accountId - int
+ * The account ID.
+ *
+ */
+ function prepareWeather(weatherData) {
+
+ return {
+ temperature: weatherData.main.temp,
+ icon: (angular.isDefined(nwWeatherIcons[weatherData.weather[0].icon])) ? weatherData.weather[0].icon : weatherData.weather[0].id,
+ description: weatherData.weather[0].description
+ }
+ }
+
+ $rootScope.$on('nwClearCache', function() {
+ cache = {};
+ });
+
+ })
+ .factory('nwWeatherIcons', function() {
+ // * All the codes by are based in the openweather API
+ // http://openweathermap.org/wiki/API/Weather_Condition_Codes
+ // * Icon based:
+ // in http://erikflowers.github.io/weather-icons/cheatsheet/index.html
+ return {
+ '200': 'wi-storm-showers ',
+ '201': 'wi-storm-showers ',
+ '202': 'wi-storm-showers ',
+ '210': 'wi-thunderstorm ',
+ '211': 'wi-thunderstorm ',
+ '212': 'wi-thunderstorm ',
+ '221': 'wi-lightning',
+ '230': 'wi-storm-showers',
+ '231': 'wi-storm-showers',
+ '232': 'wi-storm-showers',
+ '300': 'wi-sprinkle',
+ '301': 'wi-sprinkle',
+ '302': 'wi-rain-mix',
+ '310': 'wi-rain-mix',
+ '311': 'wi-rain',
+ '312': 'wi-rain',
+ '321': 'wi-rain-wind',
+ '500': 'wi-sprinkle',
+ '501': 'wi-showers',
+ '502': 'wi-rain-mix',
+ '503': 'wi-rain',
+ '504': 'wi-rain-wind',
+ '511': 'wi-snow',
+ '520': 'wi-rain-mix',
+ '521': 'wi-rain',
+ '522': 'wi-rain-wind',
+ '600': 'wi-snow',
+ '601': 'wi-snow',
+ '602': 'wi-snow',
+ '611': 'wi-hail',
+ '621': 'wi-snow',
+ '701': 'wi-fog',
+ '711': 'wi-fog',
+ '721': 'wi-fog',
+ '731': 'wi-fog',
+ '741': 'wi-fog',
+ '01d': 'wi-day-sunny',
+ '01n': 'wi-night-clear',
+ '02d': 'wi-day-cloudy',
+ '02n': 'wi-night-cloudy',
+ '03d': 'wi-cloudy',
+ '04d': 'wi-cloudy',
+ '900': 'wi-tornado',
+ '901': 'wi-thunderstorm',
+ '902': 'wi-lightning',
+ '903': 'wi-thermometer-exterior',
+ '904': 'wi-thermometer',
+ '905': 'wi-strong-wind',
+ '906': 'wi-hail'
+ };
+ });