LogoLogo
HomeDiscourseBlogDiscord
  • Introduction
  • Introduction
    • Quick start
    • Aurelia for new developers
    • Hello world
      • Creating your first app
      • Your first component - part 1: the view model
      • Your first component - part 2: the view
      • Running our app
      • Next steps
  • Templates
    • Template Syntax
      • Attribute binding
      • Event binding
      • Text interpolation
      • Template promises
      • Template references
      • Template variables
      • Globals
    • Custom attributes
    • Value converters (pipes)
    • Binding behaviors
    • Form Inputs
    • CSS classes and styling
    • Conditional Rendering
    • List Rendering
    • Lambda Expressions
    • Local templates (inline templates)
    • SVG
  • Components
    • Component basics
    • Component lifecycles
    • Bindable properties
    • Styling components
    • Slotted content
    • Scope and context
    • CustomElement API
    • Template compilation
      • processContent
      • Extending templating syntax
      • Modifying template parsing with AttributePattern
      • Extending binding language
      • Using the template compiler
      • Attribute mapping
  • Getting to know Aurelia
    • Routing
      • @aurelia/router
        • Getting Started
        • Creating Routes
        • Routing Lifecycle
        • Viewports
        • Navigating
        • Route hooks
        • Router animation
        • Route Events
        • Router Tutorial
        • Router Recipes
      • @aurelia/router-lite
        • Getting started
        • Router configuration
        • Configuring routes
        • Viewports
        • Navigating
        • Lifecycle hooks
        • Router hooks
        • Router events
        • Navigation model
        • Transition plan
    • App configuration and startup
    • Enhance
    • Template controllers
    • Understanding synchronous binding
    • Dynamic composition
    • Portalling elements
    • Observation
      • Observing property changes with @observable
      • Effect observation
      • HTML observation
      • Using observerLocator
    • Watching data
    • Dependency injection (DI)
    • App Tasks
    • Task Queue
    • Event Aggregator
  • Developer Guides
    • Animation
    • Testing
      • Overview
      • Testing attributes
      • Testing components
      • Testing value converters
      • Working with the fluent API
      • Stubs, mocks & spies
    • Logging
    • Building plugins
    • Web Components
    • UI virtualization
    • Errors
      • 0001 to 0023
      • 0088 to 0723
      • 0901 to 0908
    • Bundlers
    • Recipes
      • Apollo GraphQL integration
      • Auth0 integration
      • Containerizing Aurelia apps with Docker
      • Cordova/Phonegap integration
      • CSS-in-JS with Emotion
      • DOM style injection
      • Firebase integration
      • Markdown integration
      • Multi root
      • Progress Web Apps (PWA's)
      • Securing an app
      • SignalR integration
      • Strongly-typed templates
      • TailwindCSS integration
      • WebSockets Integration
      • Web Workers Integration
    • Playground
      • Binding & Templating
      • Custom Attributes
        • Binding to Element Size
      • Integration
        • Microsoft FAST
        • Ionic
    • Migrating to Aurelia 2
      • For plugin authors
      • Side-by-side comparison
    • Cheat Sheet
  • Aurelia Packages
    • Validation
      • Validation Tutorial
      • Plugin Configuration
      • Defining & Customizing Rules
      • Architecture
      • Tagging Rules
      • Model Based Validation
      • Validation Controller
      • Validate Binding Behavior
      • Displaying Errors
      • I18n Internationalization
      • Migration Guide & Breaking Changes
    • i18n Internationalization
    • Fetch Client
      • Overview
      • Setup and Configuration
      • Response types
      • Working with forms
      • Intercepting responses & requests
      • Advanced
    • Event Aggregator
    • State
    • Store
      • Configuration and Setup
      • Middleware
    • Dialog
  • Tutorials
    • Building a ChatGPT inspired app
    • Building a realtime cryptocurrency price tracker
    • Building a todo application
    • Building a weather application
    • Building a widget-based dashboard
    • React inside Aurelia
    • Svelte inside Aurelia
    • Synthetic view
    • Vue inside Aurelia
  • Community Contribution
    • Joining the community
    • Code of conduct
    • Contributor guide
    • Building and testing aurelia
    • Writing documentation
    • Translating documentation
Powered by GitBook
On this page
  • What we will be building
  • Prerequisites
  • Create the app
  • Create our configuration file
  • Create an API service
  • Inject our service into the app
  • Displaying the data
  • Updating the data
  • Seeing what we have so far
  • Making currencies look pretty using a value converter
  • Import and use our new value converter
  • Styling it with Bootstrap
  • Configuring Shared Styles for Shadow DOM
  • Update the markup
  • Conclusion

Was this helpful?

Export as PDF
  1. Tutorials

Building a realtime cryptocurrency price tracker

Learn how to work with Aurelia's reactive binding system to work with frequent data changes.

PreviousBuilding a ChatGPT inspired appNextBuilding a todo application

Last updated 4 months ago

Was this helpful?

Welcome to the Aurelia crypto price checker tutorial. In this tutorial, you will learn how to build an Aurelia 2 application that works with the Coingecko price API to get cryptocurrency prices and refresh them when they change.

Just want the code? You can find the code for this tutorial on GitHub . Feel free to use this code as a guide or starting point for your Aurelia applications.

What we will be building

A cryptocurrency dashboard that updates in real-time whenever the price of cryptocurrencies changes. Think of it as a stock ticker of sorts, but for cryptocurrency.

We will be interacting with the very generous Coingecko API to get the price data which allows 10 calls per second. For this tutorial, we will be calling the API once per second.

This tutorial will teach you how to work with Aurelia's binding system, briefly touch on Dependency Injection, show you how to make an API request, organize your code and create a value converter to format some currency values for display.

A demo of the application you will be building can be found .

Prerequisites

Before going any further, you should be familiar with some basic Aurelia concepts and some fundamental Javascript ones. While these are not hard prerequisites, please know that some concepts used in this tutorial out of context might be confusing or difficult to understand.

  • You have familiarized yourself with the .

  • You are familiar with (which we will use binding in this tutorial).

  • You are familiar with and how they can be used to transform data.

  • You are familiar with . You don't need to be a master of it, just familiar with its existence and why it matters in Aurelia.

  • You are familiar with async/await syntax. A great resource for learning can be found .

Create the app

We will use TypeScript & Vite for this tutorial, enabling Shadow DOM to keep our styles encapsulated. Don't worry if you're unfamiliar with these concepts yet; you will learn as we go.

For this, we will use the Aurelia makes command-line tool to create a new Aurelia application and to save time, passing in the options we want.

npx makes aurelia crypto-prices -s typescript,shadow-dom

This shorthand syntax tells the Aurelia CLI to create a new project configured with TypeScript and Shadow DOM for style encapsulation.

Create our configuration file

We will now create a .json file containing the cryptocurrencies we want to monitor.

Inside the src directory, create a new file called config.json and save it. We will watch six cryptocurrencies for this tutorial, but feel free to add more if you like.

{
    "coins": [
        "bitcoin",
        "ethereum",
        "tether",
        "binancecoin",
        "dogecoin",
        "cardano",
        "ripple"
    ]
}

This is just a JSON object with an array of cryptos called coins. Feel free to add your own cryptocurrencies to this file. Make sure they conform to the coin list on Coingecko.

Create an API service

A good practice when working with APIs in Aurelia is to create a service (a singleton class) that makes the calls to the API and returns the data. This keeps the logic separate from our view models. It also allows it to be tested and is just a good habit to get into.

Create a new directory inside of src called services and then create a new file called api.ts. Let's inject the Aurelia Fetch Client and write a method to fetch the prices.

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

@inject(IHttpClient)
export class Api {
    constructor(private http: IHttpClient) {
        this.http.configure(c => {
            c.withBaseUrl('https://api.coingecko.com/api/v3/');

            return c;
        })
    }

    async getPrices(ids: string[]): Promise<unknown> {
        try {
            const request = await this.http.fetch(`simple/price?ids=${ids.toString()}&vs_currencies=usd`);

            return request.json();
        } catch (e) {
            return e;
        }
    }
}

Let's go over this line-by-line. Firstly, this is TypeScript, which looks/works very similar to Javascript, except it aims to provide some safety by alerting you to simple mistakes or errors before you build or deploy them.

  • We import the IHttpClient interface we will inject into our app on the constructor method. The benefit of using TypeScript is that you get auto-injection instead of injecting the client manually. The Aurelia Fetch Client wraps the native Fetch API and makes it more "Aureliafied".

  • We create a getPrices method, which accepts an array of strings (our cryptocurrencies). We make this method async to make working with the promise that Fetch returns a lot cleaner than chaining .then and .catch functions in our code.

  • When dealing with async/await, it is good practice to wrap your calls in a try/catch to catch any errors that might occur (timeouts, erroneous requests, missing credentials).

  • We then request the Coingecko API, passing in our cryptocurrencies. By calling the Array.toString() method, it will automatically create a comma-separated string of values as the API expects. You could also use .join to do this as well.

  • When making Fetch requests, the resulting Fetch call with allow us to get the response value, we know we are going to be getting JSON, so we return the request.json() call, which is a promise.

As for errors, if we encounter them, the catch will capture those, and we return the error value. We have everything we need now.

Inject our service into the app

Because we used the makes tool by default, my-app.ts and my-app.html will be the view model and view that are displayed. We will use these files to add in our API calls and other pieces of logic.

Inside of my-app.ts replace the contents with the following:

import { Api } from './services/api';

import config from './config.json';
import { inject } from 'aurelia';

@inject(Api)
export class MyApp {
  private prices = {};

  constructor(private api: Api) {

  }

  async binding(): Promise<void> {
    this.prices = await this.api.getPrices(config.coins);

    this.priceUpdate();
  }

  priceUpdate(): void {
    setInterval(async () => {
      this.prices = await this.api.getPrices(config.coins);
    }, 1000);
  }
}
  • We import our newly created API service as well as the config.json file we created in the first step as config

  • We initialize an empty object in our class called prices which will store our price data

  • We inject the API and define it on our constructor

  • We specify a component lifecycle hook called binding which we make asynchronous

  • Inside of binding we make a call to our getPrices method and store the value in our class property prices

  • The returned value is an object

Displaying the data

We now have our crypto prices. Let's display them. Inside of my-app.html (our view for the app) we'll reference these price values.

<div class="panels">
    <div class="panel"><h4>Bitcoin</h4> ${prices.bitcoin.usd}</div>
    <div class="panel"><h4>Ethereum</h4> ${prices.ethereum.usd}</div>
    <div class="panel"><h4>Tether</h4> ${prices.tether.usd}</div>
    <div class="panel"><h4>Binance Coin</h4> ${prices.binancecoin.usd}</div>
    <div class="panel"><h4>Dogecoin</h4> ${prices.dogecoin.usd}</div>
    <div class="panel"><h4>Cardano</h4> ${prices.cardano.usd}</div>
    <div class="panel"><h4>Ripple</h4> ${prices.ripple.usd}</div>
</div>

This will look familiar if you have brushed up on Aurelia's template syntax. We are using interpolation to display values, but do you notice something? Prices are the object we defined inside of our view model. We are then accessing each property of this object inside of the view.

We know that the value inside of ${} is evaluated by Aurelia. In this instance, we are referencing our prices object and we know for each cryptocurrency we are watching that it returns a property followed by another child property for the price called usd.

Updating the data

We have a working application of sorts. It's not pretty, but it's functional. We have a problem, though. Any price updates after the page loads won't be rendered. Because these are price values, we want to ensure the user always has the most up-to-date information. We need to tell our view model to update the prices for us regularly.

priceUpdate() {
    setInterval(async () => {
        this.prices = await this.api.getPrices(config.coins);
    }, 1000);
}

And add the priceUpdate() call to binding function.

async binding() {
    this.prices = await this.api.getPrices(config.coins);

    this.priceUpdate();
}

We poll the Coingecko API every second to update prices and store them in the prices object. If these prices change, they are updated in the view because everything is reactively bound. Aurelia is watching this object and the properties we've bound to in our view. We use a basic setInterval to poll the server continuously. Feel free to adjust the frequency.

Seeing what we have so far

We have the basis of a functional web application that we can now run. Open up a Command Prompt/Terminal window and navigate to your project directory, then run the start command:

npm run start

This will run the Aurelia development server, and a browser window/tab should open with your application running on the default port 9000. Provided the Coingecko API is working, you should see unformatted prices in the view.

Making currencies look pretty using a value converter

You might have noticed our prices are displayed, but they're not formatted. At the time of this tutorial, the price of 1 Bitcoin was USD 34,354.00. However, it is being displayed without currency formatting as 34354.

Inside of src create a new folder called resources and then inside of resources another folder called value-converters. It is best practice to have your resources inside a resources/components folder like this. Create a new file called currency-value-converter.ts.

import { valueConverter } from 'aurelia';

@valueConverter('currency')
export class CurrencyValueConverter {
    toView(value) {
        if (!value) {
            return value;
        }

        const formatter = new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency: 'USD'
        });

        return formatter.format(value);
    }
}

Import and use our new value converter

If you want to learn other ways to inject dependencies, consult the documentation on components.

Open up my-app.html again and add in the following:

<import from="./resources/value-converters/currency-value-converter"></import>

<div class="panels">
    <div class="panel"><h4>Bitcoin</h4> ${prices.bitcoin.usd | currency}</div>
    <div class="panel"><h4>Ethereum</h4> ${prices.ethereum.usd | currency}</div>
    <div class="panel"><h4>Tether</h4> ${prices.tether.usd | currency}</div>
    <div class="panel"><h4>Binance Coin</h4> ${prices.binancecoin.usd | currency}</div>
    <div class="panel"><h4>Dogecoin</h4> ${prices.dogecoin.usd | currency}</div>
    <div class="panel"><h4>Cardano</h4> ${prices.cardano.usd | currency}</div>
    <div class="panel"><h4>Ripple</h4> ${prices.ripple.usd | currency}</div>
</div>

This looks the same as earlier, except we are importing our new value converter. You might also notice we are using the value converter syntax with a pipe | followed by currency which is the name of our value converter.

The app should automatically refresh with the new changes if you still have the app running. You should now see properly formatted currency values (complete with a dollar sign and thousand separators).

Styling it with Bootstrap

We could have ended the tutorial in the previous step, but aren't you itching to make it look nicer? Using Bootstrap 5 and some simple markup, we can.

To install Bootstrap in the project directory run npm install bootstrap

Configuring Shared Styles for Shadow DOM

Unlike conventional web applications, we use Shadow DOM to keep styles encapsulated. You must make them shared styles when working with third-party global CSS libraries like Bootstrap.

Fortunately, Aurelia already helps cater for this in your apps. Inside of main.ts you will notice some commented-out code and notes talking about shared styles.

We need to import Bootstrap's CSS first at the top of the file:

import bootstrap from 'bootstrap/dist/css/bootstrap.css?inline';

Using ?inlineis only needed for the Vite bundler and ShadowDOM. This allows us to add shared styles into our application.

Now, uncomment the .register code already present in main.ts. Also, make sure you replaced shared with bootstrap and uncomment the StyleConfiguration import from the aurelia package at the top as well.

Your main.ts file should look pretty much identical to this one:

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

// @ts-expect-error - This is a CSS file
import bootstrap from 'bootstrap/dist/css/bootstrap.css?inline';

// Convert the CSS string to CSSStyleSheet
const sheet = new CSSStyleSheet();
sheet.replaceSync(bootstrap);

Aurelia
  .register(StyleConfiguration.shadowDOM({
    // optionally add the shared styles for all components
    sharedStyles: [sheet]
  }))
  .app(MyApp)
  .start();

This now injects Bootstrap styles into all of our Shadow DOM components. The only downside is the global styles targeting elements like html or body do not get carried over (an intentional limitation of Shadow DOM).

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

Update the markup

Now we have Bootstrap added. It's time to update the markup in my-app.html to add the data into a styled table. This is all purely markup, so we are not using any new Aurelia concepts here. You can safely copy and paste this.

<import from="./resources/value-converters/currency"></import>

<div class="container">
    <h1 class="mt-5">Crypto Prices</h1>

    <table class="table table-striped mt-5">
        <thead class="table-dark">
            <tr>
              <th scope="col">Name</th>
              <th scope="col">Price (USD)</th>
            </tr>
          </thead>
        <tbody>
            <tr>
                <td><strong>Bitcoin</strong></td>
                <td>${prices.bitcoin.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Ethereum</strong></td>
                <td>${prices.ethereum.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Tether</strong></td>
                <td>${prices.tether.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Binance Coin</strong></td>
                <td>${prices.binancecoin.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Dogecoin</strong></td>
                <td>${prices.dogecoin.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Cardano</strong></td>
                <td>${prices.cardano.usd | currency}</td>
            </tr>
            <tr>
                <td><strong>Ripple</strong></td>
                <td>${prices.ripple.usd | currency}</td>
            </tr>
        </tbody>
    </table>
</div>

Conclusion

  • Reactive binding using interpolation to display values

  • Aurelia's dependency injection to include a separate file that handled our API calls

  • How to transform values in your view using value converters (taking one value and making it something else)

  • How to work with Shadow DOM and, specifically, what to do in the case of global style packages like Bootstrap

  • How to work with the Fetch client to make requests as well as async/await to work with promises in a cleaner way.

So, now we will create a value converter using the to format our currency values as properly formed values in our view.

You can read about value converters to understand what this value converter is doing.

We will not be going into detail about the . Still, it is highly recommended you read up on it as this is intended to be the standard way to work with dates, times, currencies and other types of localization data on the web.

To use this, we have to register it with . This tells Aurelia our value converter exists, and we have several options. In this tutorial, we will be including it from within our view using the import element, but you can also register it inside of the view model or globally inside of main.ts.

However, you can fix this by adding the Bootstrap CSS into the header of our index.html if you want those. Or, you can add them yourself. If you want them, add the Bootstrap CDN include in the header of index.html. You can get the most up-to-date style include .

You just created an Aurelia application that utilized a few common concepts. All of the code for this tutorial can be found on GitHub . Feel free to do whatever you want with this. Use it as a guide or even starting point for your applications.

A working example of this tutorial can be found .

here
here
Aurelia template syntax
component lifecycles
Aurelia value converters
Dependency Injection
here
Internationalization API
here
Internationalization API
Dependency Injection
here
here
here