This article builds out a fully-featured application that retrieves Intraday trading prices for Microsoft and displays them in a table.
If you have been following along on our journey so far, I have been going through the building blocks of TypeScript development. On Day 8, I touched on TypeScript working with web pages. At the end, I promised that I would move onto a more complex application, one that retrieved data via HTTP requests, and worked with that. In this post, I am going to cover a lot of ground and build out a fully-featured application that retrieves Intraday trading prices for Microsoft and displays them in a table.
To make things more interesting, I am going to introduce styling the application with Bootstrap to make it more visually interesting. The following screenshot gives an indication of what our application will look like when we have finished.
The Web Page
I am going to put the HTML for the full page here, rather than building it up section by section.
<!DOCTYPE html>
<head>
<title>100 Days of TypeScript - Day 9</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous">
</head>
<body>
<h1>Intraday Trading</h1>
<h2 id="loading-state">Loading data for MSFT</h2>
<table class="table table-striped">
<thead class="table-dark">
<tr>
<th scope="col">Time</th>
<th scope="col">Open</th>
<th scope="col">High</th>
<th scope="col">Low</th>
<th scope="col">Close</th>
<th scope="col">Volume</th>
</tr>
</thead>
<tbody id="trading-table-body"></tbody>
</table>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/
bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"
data-main="scripts/api"></script>
</body>
The header for the page should be largely familiar. What is different here is that I have linked out to something called a Content Delivery Network (CDN) to provide the Bootstrap stylesheets. Basically, Bootstrap uses stylesheets and JavaScript to control how we see content on the screen. We could store the CSS and JavaScript files locally in our project and reference them from there or we could use a centrally stored location for these files, which would be the CDN.
In the body of the page, I add a couple of headers and a table. The h2
element is given an id
because we are going to update this once the page has loaded the data.
Before we get to the table, we have a couple of scripts that are being loaded in. The first script is the JavaScript that goes along with the Bootstrap; again, this is being pulled in from a CDN. The second script is something new for us. I have broken the underlying functionality into separate TypeScript files, which has implications on how they are loaded into the browser. I am using requires.js to manage the loading of these files for me. The data-main
section tells requires which script should be loaded first.
When I break the back-end code down, I will show you how I organised the TypeScript so that it created a module that was suitable for requires.js.
Getting to the table, I told Bootstrap that I wanted the table formatted as a table with striped alternate rows. I did this by adding class="table table-striped"
. The table body has been given an id
because we are going to be interacting with it in our TypeScript.
The Intraday API
I am going to be using the trading information from Alphavantage. This allows people to get a free API key to interact with their various share APIs. The API that I will be using is intraday trading. Go ahead and have a look at the results from calling this API, it helps to give a response that looks a little bit like this.
You can probably guess that I want to map the results from this API call into TypeScript, but the JSON looks a bit interesting. The actual keys that we want to map look a lot different to what we might have typically seen in the past so mapping the metadata, for instance, means that our keys would have to look different. If you have developed applications in other languages, you would probably expect that we would have to map the name of the key onto a standard TypeScript name. In other languages, this would typically look like this:
[DataMember("1. open")]
public string Open { get; set; }
TypeScript doesn’t work like this. Instead, we can actually use the key as the name. What this means is we can create our MetaData
type like this:
export interface MetaData {
'1. Information': string;
'2. Symbol': string;
'3. Last Refreshed': Date;
'4. Output Size': string;
'5. Time Zone': string;
};
Unsurprisingly, we can do the same with our time series.
export interface TimeSeries {
'1. open': string;
'2. high': string;
'3. low': string;
'4. close': string;
'5. volume': string;
}
In the Alphavantage
API, we saw that the key for the time series was not fixed, instead it is a date. The question is, how would we model a key that has an unknown value like this? The answer is to use a TypeScript feature called an index signature. What an index signature does is tell us that you will have an object of an unknown structure, but you know the key and the value types. If we know that we have a string
key that contains a time series, we model is using the syntax.
[key: string]: TimeSeries
The name of the key isn’t magic. We could call it whatever we want, TypeScript doesn’t really care.
With this in mind, our intraday trading interface looks like this:
import { MetaData } from "./Metadata";
import { TimeSeries } from "./TimeSeries";
export interface Trading {
'Meta Data': MetaData;
'Time Series (5min)': { [key: string]: TimeSeries };
}
We have now modelled the Alphavantage
API. I appreciate that the interfaces look a bit weird, but they are exactly what we need when we call the API from our code. If you are wondering why the key is a string
and not a date
, this is because index signatures limit the acceptable types they can use and Date
is not one of those types.
Side note: I have added each interface into its own file, with the name of the file matching the name of the interface.
Calling the API
Now we come to the fun part, I have reached the point where I want to be able to call the API. To call an API, I am going to use the fetch
command. What fetch
gives us is the ability to make requests across the network and get responses back. There are a few things we are going to have to learn about this command, so let’s look at its usage in our codebase. (This code appears in the Intraday
class).
public Get(symbol: string): Promise<Trading> {
return fetch(this.apiSource + symbol).then(resp => {
return resp.json();
});
}
Note: Before I break down what is going on in this function, in order to use the fetch
capability, we need to add DOM to our lib
section in the tsconfig file. The reason we have to do this is because this is available as a browser operation, so needs to make use of DOM operations.
The fetch
operation is something that we call asynchronous. This is a fancy term that means that the operation doesn’t block us from doing something else while we wait for the result of the intraday trading call coming in. What this means to us is that we don’t return a Trading
object directly out of our method. Instead, we return a Promise
that we will have a Trading
object at some point. A Promise
is our way of telling our code that we will return a particular operation at some point in the future. You will notice that we have a return operation inside the Get
call. This is the point that we fulfill our promise.
Okay, so we know what a Promise
is, and we know that the fetch
call is asynchronous. The contents of our call here is the endpoint that we want to call, with the symbol added to it. I am going to come back to this in a minute when I show what this class looks like in its entirety. Before I do that, I need to address how we fulfil the Promise
. When the fetch
call returns, it returns a promise containing a Response
object. We use the then
method to return the json from the response to the get call. What this means to us is, following a successful API call (as designated by a HTTP response status of 200), we decode this response back into our type from the json body of the response.
Where do we get our API address from? Well, I populate this in the constructor like so.
import { alphavantage } from "./Configuration/apiKey";
import { Trading } from "./Models/Trading";
export class Intraday {
constructor(private apiSource = 'https://www.alphavantage.co/query?
function=TIME_SERIES_INTRADAY&interval=5min&apikey=' + alphavantage() +
'&symbol=') { }
public Get(symbol: string): Promise<Trading> {
return fetch(this.apiSource + symbol).then(resp => {
return resp.json();
});
}
}
Before we close this section off, there is one last piece of the puzzle I need to add in. In the repository, you will see that the apiKey
entry is missing from Configuration
. The reason for this is that I did not want to check in my Alphavantage
API key. You will need to add the following function (replading demo with your own API key), in a Configuration/apiKey.ts file in the code.
export function alphavantage() {
return 'demo';
}
Conclusion to Day 9
I appreciate that there is a lot to get to grips with in this post. In Day 10, I will continue to deconstruct this application and detail other ways of handling asynchronous code in TypeScript. I will also show how we can read the index signature by adjusting our TypeScript libraries.
If you want a sneak peak at what the code looks like, you can find the code on Github.
History
- 26th May, 2022: Initial version