Home » Tutorials » JavaScript » Build A Weather App In Vue JS

Build A Weather App In Vue JS

Build A Weather App In Vue JS

In this article, we will show you how to build a simple weather app with API using Vue JS in less than 20 minutes.

Prerequisites

We expect that you are familiar with the following:

Expected Learnings

Your key learnings from this project will be:

  • How to break the page’s UI Into hierarchy
  • How to develop a simple single page application with Vue JS
  • How to create data-driven application

Overview of Code

<!DOCTYPE>
<html>
<head>
	<style>
		body {
			font-family: Helvetical, sans-serif;
			line-height: 1.64;
			margin: 0;
			height: 100vh; /* 100% of the viewport height */
		}

		#app {
		  	background-size: cover;
		  	height: 100%;
		}

		.freeze {
		  	background-image: url(https://image.freepik.com/free-photo/closeup-frozen-surface-during-winter_181624-22234.jpg);
		}

		.cozy {
		  	background-image: url(https://image.freepik.com/free-photo/beautiful-shot-mountains-trees-covered-snow-fog_181624-17590.jpg);
		}

		.warm {
		  	background-image: url(https://image.freepik.com/free-photo/beautiful-shot-dry-desert-hill-with-mountains_181624-1974.jpg);
		}

		.hot {
		  	background-image: url(https://image.freepik.com/free-photo/rising-sun-with-seascape_1357-293.jpg);
		}

		.content {
			background: rgba(0,0,0,.3);
			box-sizing: border-box;
			color: #fff;
			height: 100%;
			text-align: center;
			padding: 24px 12px 12px;
		}

		.search-box input {
			border: none;
			border-radius: 10px;
			box-sizing: border-box;
			box-shadow: 0 0 1px 6px rgba(255,255,255,0.3);
			text-align: center;
			padding: 8px;
			width: 100%;
		}

		.weather-wrap {
			margin: 12px;
		}

		.location {
			font-size: 22px;
			margin-bottom: 0;
		}

		.date {
			color: #ddd;
			font-style: italic;
			margin-top: -8px;
		}

		.temp {
			border-radius: 6px;
			background: rgba(255,255,255,.3);
			display: inline-block;
			font-size: 28px;
			font-weight: bold;
			margin-top: 12px;
			padding: 6px 12px;
		}

		.description {
	  		font-weight: bold;
		}

		.error-msg {
			margin-top: 12px;
			color: #cc0000;
			font-size: 22px;
		}
	</style>
	<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
	<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
	<div id="app" :class="bgImageObj">
		<div class="content">
			<h1>Today's Weather</h1>
			<div class="search-box">
		  		<input type="text" placeholder="Search city. e.g. London" v-model="query" @keyup.enter="fetchWeather" />
			</div><!-- END search-box -->
			<div class="weather-wrap" v-if="typeof weather.main != 'undefined'">
				<div class="location-box">
				 	<div class="location">
				   		{{ weather.name }}, 
				   		{{ retrieveCountry }}
				 	</div>
				 	<div class="date">
				   		{{ todayDate }}
				 	</div>
				</div>
			  	<div class="weather-box">
			    	<div class="temp">
			        	{{ retrieveTemp }} ℃
			    	</div>
			    	<div class="description">{{ retrieveDesc }}</div>
			  	</div>
			</div><!-- END weather-wrap -->
			<div class="error-msg" v-if="hasError">
		  		{{ errorMessage }}
			</div>
		</div>
	</div>
	<script>
		var vm = new Vue({
		  el: '#app',
		  data: function() {
		    return {
		      query: '',
		      weather: {},
		      todayDate: '',
		      hasError: false,
		      errorMessage: '',
		    }
		  },
		  computed: {
		    bgImageObj() {
		      return {
		        freeze: this.retrieveTemp < 10,
		        cozy: this.retrieveTemp > 10 &amp;&amp; this.retrieveTemp < 24,
		        warm: this.retrieveTemp > 24 &amp;&amp; this.retrieveTemp < 32,
		        hot: this.retrieveTemp > 32
		      }
		    },
		    retrieveDesc() {
		      var obj = this.weather.weather[0];
		      for(const property in obj) {
		        if(property == 'description') {
		          return obj[property];
		        }
		      }
		    },
		    retrieveCountry() {
		      var obj = this.weather.sys;
		      for(const property in obj) {
		        if(property == 'country') {
		          return obj[property];
		        }
		      }
		    },
		    retrieveTemp() {
		      var obj = this.weather.main;
		      for(const property in obj) {
		        if(property == 'temp') {
		          return obj[property];
		        }
		      }
		    }
		  },
		  created: function () {
		    this.displayDate();
		  },
		  methods: {
		    displayDate() {
		      var dateObj = new Date();
		      var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
		      var day = days[dateObj.getDay()];
		      var date = dateObj.getDate();
		      var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
		      var month = months[dateObj.getMonth()];
		      var year = dateObj.getFullYear();
		      this.todayDate = `${day}, ${date} ${month} ${year}`;
		    },
		    fetchWeather() {
		      var _this = this;
		      const options = {
		        method: 'GET',
		        url: 'https://community-open-weather-map.p.rapidapi.com/weather',
		        params: {
		          q: _this.query,
		          lat: '0',
		          lon: '0',
		          id: '{ID}',
		          lang: 'null',
		          units: 'metric'
		        },
		        headers: {
		          'x-rapidapi-key': '{API_KEY}',
		          'x-rapidapi-host': 'community-open-weather-map.p.rapidapi.com'
		        }
		      };

		      axios.request(options).then(function (response) {
		        _this.weather = response.data;
		        _this.hasError = false;
		      }).catch(function (error) {
		        _this.hasError = true;
		        _this.errorMessage = "Not found, Please try other city.";
		      });
		    }
		  }
		})
	</script>
</body>
</html>

Step 1: Break the page’s UI Into hierarchy

The first thing you’ll want to do is to draw boxes around every component (or we call it section) in the mock and give them all names. Example:

You will see that we have 5 main section in the weather app:

  • App’s Title (h1): contains the name of the app
  • Search Box (.search-box): contains a Text input field for searching a city
  • Weather Info (.weather-wrap): display weather info for a specific city
  • Error Message (.error-msg): display error message when result not found

Now that we’ve identified the hierarchy of the section, let’s translate it into a static HTML code:

<div id="app">
  <div class="content">
    <h1>Today's Weather</h1>
    <div class="search-box">
      <input type="text" placeholder="Search city. e.g. London" />
    </div><!-- END search-box -->
    <div class="weather-wrap">
       <div class="location-box">
         <div class="location">
           Tokyo, JP
         </div>
         <div class="date">
           Sunday, 3 January 2021
         </div>
      </div>
      <div class="weather-box">
        <div class="temp">
            27 ℃
        </div>
        <div class="description">Cloudy</div>
      </div>
    </div><!-- END weather-wrap -->
    <div class="error-msg">
      Not found, please try other city.
    </div>
  </div>
</div>

Next, we need to apply some “make up” to the app, please copy following CSS code to your file.

We have defined 4 different classes (freeze, cozy, warm, hot) for switching the app’s background image when the temperature is in a certain range.

Background image sources are taken from freepik.com: Background photo created by Waewkidja – www.freepik.com, Cloud photo created by wirestock – www.freepik.com, Tree photo created by wirestock – www.freepik.com, Winter photo created by wirestock – www.freepik.com

body {
  font-family: Helvetical, sans-serif;
  line-height: 1.64;
  margin: 0;
  height: 100vh; /* 100% of the viewport height */
}

#app {
  background-size: cover;
  height: 100%;
}

.freeze {
  background-image: url(https://image.freepik.com/free-photo/closeup-frozen-surface-during-winter_181624-22234.jpg);
}

.cozy {
  background-image: url(https://image.freepik.com/free-photo/beautiful-shot-mountains-trees-covered-snow-fog_181624-17590.jpg);
}

.warm {
  background-image: url(https://image.freepik.com/free-photo/beautiful-shot-dry-desert-hill-with-mountains_181624-1974.jpg);
}

.hot {
  background-image: url(https://image.freepik.com/free-photo/rising-sun-with-seascape_1357-293.jpg);
}

.content {
  background: rgba(0,0,0,.3);
  box-sizing: border-box;
  color: #fff;
  height: 100%;
  text-align: center;
  padding: 24px 12px 12px;
}

.search-box input {
  border: none;
  border-radius: 10px;
  box-sizing: border-box;
  box-shadow: 0 0 1px 6px rgba(255,255,255,0.3);
  text-align: center;
  padding: 8px;
  width: 100%;
}

.weather-wrap {
  margin: 12px;
}

.location {
  font-size: 22px;
  margin-bottom: 0;
}

.date {
  color: #ddd;
  font-style: italic;
  margin-top: -8px;
}

.temp {
  border-radius: 6px;
  background: rgba(255,255,255,.3);
  display: inline-block;
  font-size: 28px;
  font-weight: bold;
  margin-top: 12px;
  padding: 6px 12px;
}

.description {
  font-weight: bold;
}

.error-msg {
  margin-top: 12px;
  color: #cc0000;
  font-size: 22px;
}

Step 2: Get Data & Apply Interactivity

The easiest way to build a simple single page Vue JS application, you just need to include the Vue library into the HTML file.

<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

Next, we need to setup 5 pieces of data in the Vue JS application:

var vm = new Vue({
  el: '#app',
  data: function() {
    return {
      query: '',
      weather: {},
      todayDate: '',
      hasError: false,
      errorMessage: '',
    }
  }
})
  • query: to receives user input
  • weather: an object to store the returned response from the Weather API
  • todayDate: display today’s date
  • hasError: boolean to determine when to display error message
  • errorMessage: contains error message when result not found

Step 2-1: Fetch The Weather Data

Please heading to Open Weather Map at Rapid API – https://rapidapi.com/community/api/open-weather-map and get your API.

Install Axios HTTP Client tool on your app:

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

To learn more about Axios, please see here.

  methods: {
    fetchWeather() {
      var _this = this;
      const options = {
        method: 'GET',
        url: 'https://community-open-weather-map.p.rapidapi.com/weather',
        params: {
          q: _this.query,
          lat: '0',
          lon: '0',
          id: '{{ID}}',
          lang: 'null',
          units: 'metric'
        },
        headers: {
          'x-rapidapi-key': '{{API_KEY}}',
          'x-rapidapi-host': 'community-open-weather-map.p.rapidapi.com'
        }
      };

      axios.request(options).then(function (response) {
        _this.weather = response.data;
        _this.hasError = false;
      }).catch(function (error) {
        _this.hasError = true;
        _this.errorMessage = "Not found, Please try other city.";
      });
    }
  }

Create a fetchWeather() method under the Vue JS’s methods object and fetch the weather data using Axios HTTP Client.

If we successfully receives the data from the API, we store the result into this.weather object & set this.hasError boolean to false, otherwise we set this.hasError boolean to true & an error message to the this.errorMessage.

Note: Please console.log the result and identify the structure & info of the returned result before we proceed to step 2-2.

Step 2-2: Interpret The Weather Data

Now we have a complete set of weather data in the this.weather object, let’s extract the piece we need it for the weather app. Create 3 computed methods under computed object in Vue JS: retrieveDesc(), retrieveCountry(), retrieveTemp().

computed: {
    retrieveDesc() {
      var obj = this.weather.weather[0];
      for(const property in obj) {
        if(property == 'description') {
          return obj[property];
        }
      }
    },
    retrieveCountry() {
      var obj = this.weather.sys;
      for(const property in obj) {
        if(property == 'country') {
          return obj[property];
        }
      }
    },
    retrieveTemp() {
      var obj = this.weather.main;
      for(const property in obj) {
        if(property == 'temp') {
          return obj[property];
        }
      }
    }
  }

In our case, we need following info from the weather data:

  • retrieveDesc(): weather’s description
  • retrieveCountry(): the country of the city
  • retrieveTemp(): the temperature

Step 2-3: Switch App’s Background Image

Next, we want the app’s background image changed according to the temperature. Let’s create another computed methods under computed object and named it as bgImageObj().

  computed: {
    bgImageObj() {
      return {
        freeze: this.retrieveTemp < 10,
        cozy: this.retrieveTemp > 10 &amp;&amp; this.retrieveTemp < 24,
        warm: this.retrieveTemp > 24 &amp;&amp; this.retrieveTemp < 32,
        hot: this.retrieveTemp > 32
      }
    }
  }

The bgImageObj() returns a CSS classname in certain range of temperature: freeze, cozy, warm, hot.

Step 2-4: Compose Today’s Date

We need to create a method displayDate() under Vue JS’s methods object for getting today’s date.

Then we call the displayDate() method when Vue JS application is created.

  created: function () {
    this.displayDate();
  },
  methods: {
    displayDate() {
      var dateObj = new Date();
      var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
      var day = days[dateObj.getDay()];
      var date = dateObj.getDate();
      var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
      var month = months[dateObj.getMonth()];
      var year = dateObj.getFullYear();
      this.todayDate = `${day}, ${date} ${month} ${year}`;
    },
  }
})

Step 3: Identify Where The Data Should Live

So we’ve prepared all the data we need in Step 2, next we need to identify where we want to host & display the data.

<div id="app" :class="bgImageObj">
  <div class="content">
    <h1>Today's Weather</h1>
    <div class="search-box">
      <input type="text" placeholder="Search city. e.g. London" v-model="query" @keyup.enter="fetchWeather" />
    </div><!-- END search-box -->
    <div class="weather-wrap" v-if="typeof weather.main != 'undefined'">
       <div class="location-box">
         <div class="location">
           {{ weather.name }}, 
           {{ retrieveCountry }}
         </div>
         <div class="date">
           {{ todayDate }}
         </div>
      </div>
      <div class="weather-box">
        <div class="temp">
            {{ retrieveTemp }} ℃
        </div>
        <div class="description">{{ retrieveDesc }}</div>
      </div>
    </div><!-- END weather-wrap -->
    <div class="error-msg" v-if="hasError">
      {{ errorMessage }}
    </div>
  </div>
</div>

As you seen above,

  • we’ve bind a CSS class object bgImageObj to the #app div.
  • bind a v-model directive “query” to the search input field for receiving user input.
  • attach a keyboard event to listen when user press on the “Enter” key & trigger fetchWeather() method that request the weather API.
  • hide .weather-wrap div when weather.main object is undefined.
  • display city name {{ weather.name }} using the “Mustache” syntax (double curly braces).
  • display country code {{ retrieveCountry }} next to the city name.
  • display today’s date {{ todayDate }} in .date div.
  • display temperature {{ retrieveTemp }} in .temp div.
  • display weather description {{ retrieveDesc }} in .description div.
  • hide .error-msg div when hasError boolean is false.
  • display error message {{ errorMessage }} in .error-msg div

And That’s It

Hope you learned something from this article & it’s now time for you to deploy your application, and share it with your peers. 🙂