Building A Weather Application With Aurelia 2

While Aurelia 2 is still not quite ready for release, you can use it right now as the basic core pieces are functional. I thought it would be fun to build a weather application using Aurelia 2.

If you don’t want to run through a tutorial and just want the final result, all of the code for the weather application can be found on GitHub here.

What Are We Building?

In this tutorial, we are going to be building an Aurelia 2 application that displays weather information. Original, right? You will learn how to create new Aurelia 2 applications, as well as work with the OpenWeatherMap API where we will consume JSON for the information.

Before we can continue, head over to the OpenWeatherMap website here and create a free account. This application will only be for personal use, so the limits of 60 calls per minute are perfect for us.

You will then want to generate an API key by heading over to the API Keys section once you are signed up and logged in.

A Note On Code In This Tutorial

Aurelia allows you to leverage conventions for things such as custom elements, value converters and whatnot. It also allows you to be more explicit in how you name and import dependencies.

For the purposes of this tutorial post, we will be leveraging conventions, but the code in the repository will leverage no conventions and use decorators for describing components. My personal preference is to be quite explicit in my Aurelia applications.

Getting Started

Unlike Aurelia 1, there is nothing to install globally (the aurelia-cli is not a dependency you need any more). To bootstrap a new Aurelia 2 application, you simply open up a terminal/PowerShell window and run:

npx makes aurelia

Because TypeScript is the future, I recommend choosing the “Default TypeScript Aurelia 2 App” option in the prompt. Then choose, “npm” for the package installer option and wait for your app to be created.

To confirm everything installed correctly, open up the generated application directory (in my case it is weather-app) and then run the application using npm start a browser window should open and point to port 9000.

Create A Weather Service

In an Aurelia application, using singleton service classes is a great habit to get into too. Singletons are easy to test and work well with Aurelia’s dependency injection (DI).

In the src directory create a new folder called services and a file called weather-api.ts which will handle making calls to the OpenWeatherMap API service.

import { HttpClient } from '@aurelia/fetch-client';
import { DOM } from '@aurelia/runtime-html';

const http = new HttpClient(DOM);

export class WeatherApi {
    private apiKey = '';
	private units = 'metric';

    public async getWeather(address) {
        const req = await http.fetch(`https://api.openweathermap.org/data/2.5/forecast?q=${address}&units=this.units&APPID=${this.apiKey}`);

        return req.json();
    }
}

This simple service will allow us to query the API for weather information. But, we are not using TypeScript to its full potential here. Let’s write some interfaces and type the response.

import { HttpClient } from '@aurelia/fetch-client';
import { inject } from 'aurelia';

@inject(HttpClient)
export class WeatherApi {
    private apiKey = '';
    private units = 'metric';

	constructor(private http: HttpClient) {

	}

    public async getWeather(latitude: number, longitude: number): Promise<IWeatherResponse> {
        const req = await this.http.fetch(`https://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&units=${this.units}&APPID=${this.apiKey}`);

        return req.json();
    }
}

interface IWeatherResponse {
    cod: string;
    message: number;
    cnt: number;
    list: IWeatherResponseItem[];
}

interface IWeatherResponseItemWeather {
    id: number;
    main: string;
    description: string;
    icon: string;
}

interface IWeatherResponseItem {
    dt: number;
    main: {
        temp: number;
        feels_like: number;
        temp_min: number;
        temp_max: number;
        pressure: number;
        sea_level: number;
        grnd_level: number;
        humidity: number;
        temp_kf: number;
    };
    weather: IWeatherResponseItemWeather[];
    clouds: {
        all: number;
    };
    wind: {
        speed: number;
        deg: number;
    };
    rain: {
        '3h': number;
    };
    sys: {
        pod: string;
    };
    dt_txt: string;
}

Now, there is one thing I want to point out with the above example. We’re hard-coding the API key into the singleton class, in a real application, you would and should never do this. Anyone who has your API key will be able to make requests and blast through your limits quickly. Never store API keys client-side.

We now have the class we will use to query for weather information. I won’t go into super specifics around markup and whatnot as those things can be seen in the GitHub repository for this tutorial here.

Leveraging Dependency Injection (DI) To Import Our Service

Now that we have our weather API service, we need to include it for use in our application. We will be editing the generated my-app.ts file as part of the Aurelia application creation process.

We want to replace the entirety of our my-app.ts file with the following:

import { WeatherApi } from './services/weather-api';

export class MyApp {
  private weather;

  constructor(private api: WeatherApi) {

  }

  attached() {
    navigator.geolocation.getCurrentPosition((position) => this.success(position), () => this.error());
  }

  async success(position: Position) {
    const latitude  = position.coords.latitude;
    const longitude = position.coords.longitude;

    this.weather = await this.api.getWeather(latitude, longitude);
  }

  error() {

  }
}

Because my-app.ts is a rendered custom element (as can be seen inside of index.ejs we get DI automatically when we use TypeScript. This means we don’t have to use the inject decorator to inject things.

All dependencies get passed through the constructor and using TypeScript with a visibility keyword, they get hoisted onto the class itself for use. It’s a nice touch and one of my favourite things about TypeScript.

The attached method is a lifecycle method that gets called in components once the DOM is ready. This is where you handle interacting with the DOM or calling various API methods. We call the getCurrentPosition method here to request the users latitude and longitude.

The success callback is called via the navigation position callback on success. This is where we get the latitude and longitude values, then we make a call to our injected API class and call the getWeather method.

You might also notice we are using async/await here which allows us to wait for the results of a promise and get the data. We assign the value to the class variable weather which will be referenced in the view template shortly.

Creating A Value Converter For Formatting Dates/Times

We are going to use the date-fns library for working with dates and formatting them. One of the values the weather API returns is a date string which we will parse and then format for display purposes.

You might not have worked with date-fns before, but it is similar to Moment.js which is a heavier and often unnecessary option to go with.

To install date-fns all we need to do is run:

npm install date-fns

In Aurelia, a value converter is exactly what it sounds like. It converts values to something else (either to the view or from the view). In our use case, we only want to convert a value in the view.

For resources, I highly recommend creating a resources directory which exports an array of resources to make global.

Create a file called src/resources/value-converters/date-format.ts and add in the following:

import { valueConverter } from '@aurelia/runtime';
import { format } from 'date-fns';

export class FormatDateValueConverter {
    toView(date): string {
        return date ? format(new Date(date), 'MM/dd/yyyy - h:mm bbbb') : date;
    }
}

Inside of the resources directory create a file called index.ts with the following:

import { FormatDate } from './value-converters/format-date';

export const resources = [
    FormatDate
];

This exports an array of one or more resources. For the purposes of this tutorial, we are only exporting one resource to make global. In a real application, you might have several. If you worked with Aurelia 1, this paradigm will look familiar with you with an array of resources that get globalised. Except, globalResources is no longer a thing.

Inside of src/main.ts we want to import those resources and register them:

import Aurelia from 'aurelia';
import { MyApp } from './my-app';
import { resources } from './resources';

Aurelia
    .register(resources)
    .app(MyApp)
    .start();

The Markup

We now have the basics in place, let’s start with our markup and styling. We are going to use Bootstrap 4 because it has a good grid system and will make styling things easier.

Install and Configure Bootstrap

npm install bootstrap

Before we start adding in any HTML, we need to import the main Bootstrap CSS file into our application.

At the top of src/main.ts add the following import:

import 'bootstrap/dist/css/bootstrap.css';

We now have the styling we need for marking up our columns and aspects of the grid system.

Creating A Weather Component

Breaking your application into components is a great way to create applications. Smaller components are easier to test, they are also easier to maintain and neater.

Inside of the src directory create a new folder called components and create a component called weather-item which we will import and use to display our weather information.

Now, we want to create the HTML file for our custom element: src/components/weather-item.html

<bindable name="data" />

<p><strong>Date:</strong> ${data.dt_txt | formatDate}</p>
<p><strong>Clouds:</strong> ${data.clouds.all}%</p>
<p><strong>Temperature:</strong> ${data.main.temp}&deg;</p>
<p><strong>Feels Like:</strong> ${data.main.feels_like}&deg;</p>
<p><strong>Humidity:</strong> ${data.main.humidity}%</p>

A brief explanation of what is happening here, we just created a HTML only custom element. The bindable element at the top is telling Aurelia that we have a custom element which accepts a bindable property called data which allows data to be passed through.

You will notice below when we import and use our element, we are binding on the data property by specifying data.bind. Inside of our custom element, we reference this bindable value specifically to get pieces of data passed in.

If you have experience working with other frameworks or libraries such as React, you might know of these as “props” in Aurelia they’re bindable properties.

If you want to build custom elements with business logic that extends beyond simple bindables, you will want to consult the documentation on creating custom elements that leverage a view-model (something we do not need to do here).

<import from="./components/weather-item.html"></import>

<div class="container spacer-v">
    <form class="mb-4">
        <h2 class="mb-3">Weather. Whenever.</h2>
    </form>

    <div if.bind="weather && weather.cod === '404'">
        <h3>Error</h3>
        <p>${weather.message}</p>
    </div>

    <div class="row" if.bind="weather && weather.cod !== '404'">
        <div class="col-md-3">
            <div class="row">
                <div class="col-md-3"><img src="http://openweathermap.org/img/wn/${weather.list[0].weather[0].icon}.png"></div>
                <div class="col-md-8">
                    <h3>${weather.city.name}, ${weather.city.country}</h3>
                </div>
            </div>
        </div>
        <div class="col-md-8">
            <div class="row">
                <weather-item data.bind="item" class="col-md-4 mb-3" repeat.for="item of weather.list"></weather-item>
            </div>
        </div>
    </div>
</div>

The first line is simply using the import element to include our component for use in our view. The import element works like a Javascript import, except it can also import HTML files as well as CSS files. If you have experience with Aurelia 1, this is the same element as require.

The rest is standard Aurelia templating, if you’re familiar with Aurelia v1, this will look familiar to you already.

If you’re new to Aurelia, I highly recommend reading the documentation to get a better understanding of how to author Aurelia templates and work with the templating.

To complete the styling, open up my-app.css which is a CSS file included automatically by Aurelia (matching the name of my-app.ts using conventions).

.spacer-v {
    padding-top: 50px;
}

Running The App

Provided you followed all of the steps correctly, to run the application simply type the following in the project directory:

npm start

A browser window should open and you should see an application that requests your current location and shows you the weather. It should look like this.

There are some improvements you could make on your own like a field that allows users to type addresses, a map or more detailed weather information, adding in routing to view specific fine-grain information. This is a quick and basic tutorial showing you how easy it is to build in Aurelia 2.