Building a widget-based dashboard
Learn how you can leverage dynamic composition to build dynamic user interfaces like dashboards.
Just want the code? You can find the code for this tutorial on GitHub here. Feel free to use this code as a guide or even starting point for your own Aurelia applications.
What we will be building
A simple dashboard application using dynamic composition to render a dynamic dashboard UI.
The dashboard will be comprised of a handful of different widgets and by leveraging a configuration-based approach, you learn how you can use the <au-compose>
element to achieve this.
Try before you buy? See a working example of the app you will be building here.
Prerequisites
Before going any further, you should be familiar with some basic Aurelia concepts as well as some fundamental Javascript ones as well. 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 Aurelia template syntax.
You have familiarized yourself with components in Aurelia.
You are familiar with Dependency Injection. You don't need to be a master of it, just familiar with its existence and why it matters in Aurelia.
Create the app
When creating a new Aurelia 2 application, it is considered best practice to use the CLI. You can simply type npx makes aurelia
to do this, but for the purposes of this tutorial, we are going to give you the shorthand syntax to do this faster.
For this tutorial application, we will be using TypeScript for writing our view models and CSS Modules for our CSS to keep things neat. If you are interested in using Shadow DOM, please see our other tutorial Building a realtime cryptocurrency price tracker to see Shadow DOM in action.
We now have the barebones Aurelia starter for the basis of our dashboard application.
Scoping the requirements
Like all good projects, they should be well-planned and thought out. Let's determine what our dashboard will do.
A series of dashboard blocks will be displayed to the user
Each block is its own widget
Blocks do not share their state, they are responsible for themselves only
Blocks will be components rendered using the
<au-compose>
elementThe blocks we want are; a date component, a notes component for taking notes, a dog widget for displaying a random dog image, a GeoIP component that displays your IP address and estimated region data, and finally a USD exchange rate component.
No CSS frameworks or external dependencies should be used (just CSS)
Before we begin
We will be creating some folders inside of the generated src
directory where our components, resources and other parts of our application will live. We will do this now to save time and get our structure properly sorted out because it'll make things easier further down.
Create the following directories inside of the src
directory.
components
— this is where all of our dashboard components will reside.services
— this is where all of our service singletons will live (logic for interacting with services)
Component #1 - Date component
The first component we will be creating for our dashboard is a date component. It does one thing, it displays the current date. This will be a nice gentle introduction to creating components.
Inside of components
create a new file called date-component.ts
The functionality of this component is to get the current date and then display it. So inside of it, we are not even going to use lifecycles or any other Aurelia specific concepts, this will be a basic class.
Seriously, that's all this component is going to do. Now, we need a view for this. Unlike other components, we are not even going to create a separate view file. We can specify an inline template for our view.
We will import the customElement
decorator and then decorate our component class. We have to specify the name (the HTML tag) and the template takes a template string. Take note of the backslash \
this is being used to escape our interpolation as we only want Aurelia interpreting this, not Javascript.
Component #2 - Dog component
In this component, we will be interacting with the public random dogs API to get an image of a random dog and display it. We will be making a request using the Fetch Client as well as binding to the returned image.
You will be introduced to the Promise controller in this component, showcasing how you can work with promises inside of your views.
Create a new file called dog-component
inside of src/components
and add in the following code:
Inside of the fetchDog
method we are doing the following:
Making a request using the Aurelia Fetch Client (which wraps native Fetch) to the random dog API
Because we are working with the promise controller, we handle returning the JSON on success or throwing an error if there was a failure
By adding return to the fetch call and subsequent resolution, we either return JSON or an error
Now, we create a dog-component.html
file inside of the components
directory:
If you have read up on the promise controller, this syntax will be familiar to you. We make the call to our fetchDog
method while we wait for it to resolve, the pending
attribute will show the element it is used on. Once the promise resolves on then
we get the return object, we can work with it. We then bind the returned URL src
attribute of the image. If there is an error, the catch
will be triggered and pass our error.
Base styling
Our dashboard application is going to be a series of components with varied sizes. Because this isn't a CSS tutorial, here are the styles to make everything look pretty. Add these into your my-app.css
file as they will form the basis of the core dashboard styling.
If you are comfortable with CSS, feel free to change things. For this tutorial, this merely serves to make the dashboard feel like a dashboard and look nicer.
Have you had enough water today? If not, take a moment to get yourself a nice refreshing glass of water or take a sip from your water bottle.
Using dynamic composition
One of Aurelia's strengths and most useful features is the ability to dynamically compose components in your applications. We will be using the <au-compose>
element for this. Passing in a view model we will be able to dynamically render our components.
Dynamic composition is what we will be using to display our widgets. It means we don't have to import a bunch of components and reference them by their element name, we'll loop over an array of components and display them that way.
Inside of my-app.ts
add in the following:
We import the two components we have created so far
We create a new class property called
components
which is an array of our imported componentsWe use an array because it allows us to change the order of components being displayed as well as remove any we don't want to show (it becomes modular)
The missing piece is now adding the actual dynamic composition to our view. Open my-app.html
and add in the following:
We add a div with a class of container which will hold our components
We loop over our components using
repeat.for
we do this on a<template
element so we don't introduce any additional elements to the DOM (template elements don't get shown in the browser)Our
repeat.for
is the equivalent offor (let component of components)
in JavascriptWe use the
<au-compose>
element and pass in the instance toview-model
which will then render the componentOn
<au-compose>
we also use thecontainerless
attribute, which will remove any<au-compose>
element in the DOM and only leave us with the custom element itself
If you were to run this app using npm start
you would see something like this so far:
Component #3 - GeoIP component
For our third component, we will leverage the GeoIP API to create a component that displays information about the current user. Their IP address, approximate location and other details (only you can see your own details).
Some of this code will look familiar to you. We worked with the Aurelia Fetch Client to build our dog component (component #2), we'll be using the promise controller for this again as well because it makes working with promises in Aurelia cleaner.
Create a new file called geoip-component.ts
inside of the src/components
directory in your application and populate it with the following:
We actually don't need to explain what is happening here. This is pretty much a copy and paste of our dog component. We explain what each piece of code is doing, so if you skipped that step, please go back before continuing
Now, let's create the view for our geoip component. Create an HTML file called geoip-component.html
in the src/components
directory:
Using an adblocker extension in your browser? Many ad blockers will block requests to geolocation services like these. Make sure you disable it for localhost or exclude it from blocking or requests will not work.
Inside of my-app.css
add the following CSS beneath the dog-component
styling:
Now, import the geoip component inside of my-app.ts
adding it to our array of components. Your file should look like this (unless you switched up the order).
Component #4 - Notes component
Our fourth component will be a note-taking component. It will show a simple list of notes, allow us to delete them and most importantly: allow us to add new notes. This component will not require communicating with a third-party API.
Like all the components we have created before, we start off by creating two files in the components
directory. Let's create notes-component.ts
first as this is where most of the code will be.
Inside of notes-component.ts
add the following:
First, we create a class property called
notes
which is an array of strings. These strings are our notes.The class property called
note
is where our in-progress notes are stored (bound to a textarea)The
addNote
method puts the value ofnote
bound to our textarea into our array, usingunshift
to push it to the beginningWe can then set the
note
value to be an empty string (you will see the textarea empty)The
remoteNote
method takes a numeric index of a noteUsing
splice
on the notes array, we delete our not based on its index value
We now need the markup for our component. Create a new file called notes-component.html
alongside our notes-component.ts
file and add the following:
value.bind="note"
binds the nativevalue
attribute on our textarea to the class property callednote
we also disable spellchecking in this fieldWe add in a button with
click.trigger="addNote()"
which will call theaddNote
function every time the button is clickedWe then create an unordered list where we loop over the
notes
array and display the valueInside of the
li
we also have a button with aclick.delegate
we use a delegate here because we don't know how big the list will be and delegation will create one event listener, not multiple.On the
click.delegate
we callremoveNote
and pass in the current index value from the loop accessible via the special property$index
Unlike other components, we need some specific styles for our notes component. Because conventions rock, Aurelia will automatically look for a .css
file for any components you create. By creating a file called notes-component.css
alongside the other files, Aurelia will see this and automatically import it for us.
Inside of notes-component.css
add in the following styles:
Let's update our my-app.ts
file, this time we'll be changing the order slightly and we will also be importing our newly created notes component too.
Component #5 - USD exchange rate component
The final component in our extravagant dashboard is an exchange rate component that displays what one US dollar will get you in other countries.
Once more, we'll be interacting with an API. And the code like in our dog component and GeoIP component will be the same except the endpoint will be different.
Create a new file called exchange-component
in the components
directory:
Like component 4 and component 2, the code is basically the same (the method name is different and the endpoint is different, but that's it).
Now, we create the view for our component exchange-component.html
Like the other promise-based examples before this one (component 4 and component 2), we use the promise controller syntax. Inside then
we assign the response to a variable called data
and then we can access the properties. In our case, we are accessing exchange rates.
Now, let's create the accompanying CSS file for our component of the same name, exchange-component.css
Now, let's open up my-app.ts
one more time and add our exchange component and change the order again:
Running the app
We now have a functional and styled dashboard. By running npm start
in the application directory, you should see your application running. Here is what it should look like (will differ depending on screen sizes).
It's not the prettiest app in the world, but we have a functional application.
Refactoring (optional)
We have a working app, we are both happy. But, we have a lot of duplication in our code, especially when it comes to making requests to APIs. Now, we are going to be tweaking our code to make it even smaller and more testable.
This step in the tutorial is optional. You do not have to refactor, however, if you want to see ways in which we can remove code from our application and still retain its functionality, you will want to stick around for this.
Our dog, GeoIP and exchange components all make API requests, they all expect JSON to be returned. Armed with this knowledge, can create a service that can be used to make these requests for us and format the returned data.
Create a new file called api.ts
in our services
directory. We will then inject the Aurelia Fetch Client and create a method that accepts two parameters.
We inject the Aurelia Fetch Client on the constructor as we did in our other components
Because we are using TypeScript, dependencies on the constructor get automatically injected
We create a new class method called
fetchData
which accepts the URL to call and an optional error message if the request fails.
Now, let's refactor our dog-component.ts
we'll be injecting the API and that is it:
Inside of dog-component.html
replace the existing promise.bind
with this one:
Because our API is injected into the view model, it becomes available to the view. We can directly call the new fetchData
method with the URL and custom error message (if we want to provide one).
We do the same for exchange-component.ts
refactoring to:
Once more, inside of exchange-component.html
we replace the existing promise.bind
with this one:
Last, but not least, we need to refactor geoip-component.ts
as well (hey, you're really good at this):
Inside of geoip-component.html
replace the existing promise.bind
with this one:
That's it. You've just built and lightly refactored an extensive Aurelia 2 application. Well done.
Last updated