How to Write a Simple Stock Tracker with Vaadin 15 Using Java and TypeScript
Vaadin 15 adds the option to enhance your Java application with views written in TypeScript, without the need for a server-side state.
This post will guide you through creating a simple stock tracker application, saving your favorite stocks to the server and using the Financial Modeling Prep API to get up-to-date information about them.
For the time being, the finished app can be tried out here.
Note: Since writing this tutorial, the FMP API now requires a free API key to use.
As a starting point, we use the Vaadin 15 starter with Spring Boot, available from Vaadin.com/start. Download it, unzip it, import it to your IDE of choice, and run it from the command line with mvn spring-boot:run
, or by running the Application
class from your IDE. Finally open it at localhost:8080
.
If you choose to run it from your IDE, you must run
mvn install
once from the command line first.
The back end
The only things we need on the server is a class to represent one of our favorite stocks, and a service for retrieving, saving, and deleting these.
Create a class Stock
as the data model. It has one field, symbol
, a string which stores the symbol the stock is traded under, such as AAPL
for Apple.
Next, make a class StockService
to operate on these. For the purpose of this article, it keeps the stocks in memory, instead of connecting to a database.
It has some default data, and methods to retrieve the stocks in alphabetical order, to add a new stock, and to remove an existing stock.
The removeStock
method is synchronized over the list of stocks, to avoid concurrent edits to the list while iterating over it.
The magic that enables us to call these methods from the client are the @Endpoint
and @AnonymousAllowed
annotations, introduced in Vaadin 15.
The endpoint annotation instructs Vaadin to create a TypeScript service with the same public methods. Vaadin will handle the client-server communication. The second annotation allows anyone to call the endpoint without providing an authentication token.
Running the application generates the file ./frontend/generated/StockService.ts
, that we will use from the client later. The Stock
type is also defined in TypeScript as having the property symbol
of type string
. This gives us type safety all the way from the client to the server.
The front end view
Create the file stock-tracker.ts
in ./frontend/views
. This is our main view, built with LitElement, a lightweight library for creating web components.
We have now created a class for our view in the form of a web component. To use LitElement, we extend from the LitElement
class. The @customElement
decorator states the tag to use for the component, generally the same as the file name.
The render function produces the content of the component, run through LitElement’s html
template tag.
To enable navigating to the view, we edit the routes in ./frontend/index.ts
. By default, it falls back to any server-side routes we have defined. Insert a block for our stock-tracker
before that.
There are three pieces to the route object. The path
defines the URL for our view. The component
defines what content is shown, in this case it is a <stock-tracker>
.
The third piece is the action
that imports our web component. It could also be imported with an import './views/stock-tracker';
statement at the top of the index.ts
file, but by using the action, we defer the import until the component is navigated to.
We can now navigate to localhost:8080
, and be appropriately greeted by the “Hello” content we defined in the web component.
As long as we run the app in development mode, and only modify front-end files, no server restarts are needed. Just refresh the browser to see your changes.
If we inspect the source code of the page, we see that a <stock-tracker>
element has indeed been inserted into the div
that is defined as the outlet in index.ts
.
The stock service
Now let’s involve our StockService
. We import it to the stock-tracker.ts
file by its relative path: import * as StockService from ‘../generated/StockService';
. This imports all all the functions that is exports, under the StockService
name.
Next, create a firstUpdated
method from which we call getStocks
. The firstUpdated
method is part of the LitElement lifecycle, and is called once after the component has been created.
The getStocks
call returns a promise, so we chain a then
call to handle the result once it’s available. For now, log it to the browser developer console, using console.warn
to make it easily distinguishable
Refresh the page and open the browser console (usually F12), we now see our stocks being logged.
The list of stocks
To display the stocks in our stock-tracker
, we use a Vaadin Grid. Add an import statement import '@vaadin/vaadin-grid';
and create the structure for it in the render function.
The grid’s items property is bound to a field called items,
which we will create shortly. The dot prefix denotes that we are setting a property, not an attribute. The grid has one column with the path symbol
, as that is the key under which the symbol value is stored, as seen in the screenshot above.
Next, add the field private items = [];
to the class. We will mark this as a LitElement property by adding a@property({ type: Array })
decorator in front of it. Add an import for property
after customElement
.
The TypeScript compiler is strict about the code, and may give you compilation errors while coding. You can turn some errors off in the
./tsconfig.json
file by setting compiler options to false. For example, settingnoUnusedLocals
to false prevents errors for having unused variables. Restart the server for this to take effect.
Instead of logging our stocks, we set them to our items
property with this.items = result
. If we look at the view now, it shows our default stocks.
The stock API
It would be useful to show some more information about the stocks. We can get this info from the FMP API. For example, to get information about the AAPL and GOOGL stocks, we call https://financialmodelingprep.com/api/v3/quote/GOOGL,AAPL.
First define the static part of the URL in a private field stockAPIBaseUrl
, containing all of the URL except the stock symbols.
We then change the then
part of the getStocks
call to extract the symbols from the StockService
response, concatenate them, and append them to the URL.
Once we have the URL, we fetch the details about the stocks using the fetch
method, convert the response to JSON, and set the result as the value of this.items
. A console.warn
statement is left in allowing us to see the API response in the browser console. The code for fetching stocks has here been split into a separate, private method, to enable re-use.
Opening the browser console now to look at our log statement, we see that a lot more information is logged about our stocks.
The grid still looks the same, as we have not defined any other columns.
The columns
We can add columns for any data we wan’t to display, for example <vaadin-grid-column path="price"></vaadin-grid-column>
to see the current price of a stock.
By using a renderer, we control how the content of a column is formatted. Add a column for the daily percentage change of a stock, this time without providing a path
. We set a header, as the header is by default based on the path, and we set the renderer property, which references a method that renders the content: <vaadin-grid-column .renderer=${this.renderPercentage} header="Percentage change"></vaadin-grid-column>
.
Here, the renderer method has been created. It has three parameters: a cell element, the column the cell belongs to, and the data for the row that is currently being rendered.
As JavaScript doesn’t have TypeScript’s private keyword, a leading underscore is commonly used to denote a private method. Leading underscores can still be used in TypeScript to denote an unused method argument, such as
_column
above, something TypeScript would otherwise raise an error about.
The row data contains the index and the item to render, from where we extract the changePercentage
value. The next line defines a negative
class name that is added if the value of the stock has gone down. Similarly, if it has gone up, a prefix of +
is added in front of the value.
We render the content using the render
function from LitElement’s templating library lit-html
. The function is imported with import { render } from 'lit-html';
.
The render
function works similarly to the render
method defined in our class. It takes two arguments: the template to render, and the element to render it into.
Avoid setting the content through the
innerHTML
property, as this is prone to XSS.
With the renderer in place, our grid now has a column for the percentage change.
The styles
The span
surrounding our percentage change column values has a class percentage
, and also a class negative
if it’s below 0. We use that to style the column.
Similarly to the render function, we define styles in the static get styles()
function. From there we return a string passed through the css
template tag, which we import and use the same was as the html
template tag.
Add CSS styles that define elements with percentage
classes as having bold and green text. If they also have the negative
class, the text is red.
The percentage change column now looks better!
It’s good time to add some styles to the stock-tracker
itself. Web components have encapsulated styles, meaning that its styles don’t affect elements outside the web component, and vice versa. That way we do not have to worry about naming our CSS classes to avoid conflicts.
To target the web component itself, we use the :host
selector. The flex styles causes elements to be laid out in a column from top to bottom, the box sizing causes the padding to be included in the width, and we use a CSS custom property from the Lumo theme to set the padding. Using these custom properties helps keep the styling consistent.
The search box
What good does the app do, if we can’t add new stocks to the list? Not much. To mend this, we will build a search box with an add button.
Add a vaadin-combo-box
and a vaadin-button
below the grid, wrapped in a vaadin-horizontal-layout
. Give the combo box an id and a label. Also add imports, the horizontal layout is part of vaadin-ordered-layout
.
The code includes a private field that references the search box. Using the query
decorator imported from LitElement, the value of the field will automatically be set based on a CSS selector, in this case we look for the element with the id stock-search
.
TypeScript requires fields to have types. While the Vaadin team are working on finishing up the TypeScript definitions for all components, we can use the
any
type, although this does not give us type safety and code completion.
FMP provides a search API in the form of https://financialmodelingprep.com/api/v3/search?query=<query>&exchange=NASDAQ
. The <query>
will be replaced by what we want to search for. Other exchanges than NASDAQ are also supported.
To wire this up to the combo box, define the above URL as a private field. Then create a function that accepts the filter change event, type any
. The filter text is available in event.detail.value
.
In the function, we update the filteredItems
property of the combo box. If no filter is set, we display no items. Else, we replace the <query>
in the URL with the filter string, and once more use the fetch
function to get results.
Now add a listener for the filter-changed event directly to the combo box, using LitElement’s @<event-type>
syntax. The value is a reference to the method we created, using ${...}
string interpolation:<vaadin-combo-box @filter-changed=${this.onStockSearchStringChanged} id=...
.
Curious about what can be done with a Vaadin component? Check out the examples! Examples are available both for Java and HTML.
At this point, we can type in the combo box, and get results, although the presentation still requires some polishing.
By testing out the search API URL in the browser, we see that the result objects contain symbol
and name
keys. Just as we set the path
for the grid columns, we set the item-label-path
on the combo box to display the symbol
value.
Which company a symbol refers to isn’t always that obvious, though. It would be nice to also display the name of the company. To do that, we once again need a renderer.
Create a function renderSearchBoxItems
with the following parameters: the cell to render into, the combo box itself, and the model containing the item to render. Assign this to the combo box with .renderer=${this.renderSearchBoxItems}
.
The content will be a simple HTML structure, with classes to aid with styling.
Adding CSS to the combo box requires a little more work. The items are rendered into the shadow root of the combo box component. This means that just as our styles won’t leak outside the stock-tracker
, they won’t penetrate into the vaadin-combo-box
.
Vaadin‘s components implement a themable mixin, that allows us to style parts of these components. There is a registerStyles
utility function for this. Import it, and register styles, targeting the vaadin-combo-box-item
elements inside the combo box. This must be done outside our component class.
We widen the combo box itself by adding styles into our static get styles
method: vaadin-combo-box { width: 300px; }
. The search results are now much more useful.
The “Add stock” button
Once we have selected a stock, we must be able to add it to the list. But first, the styles should be improved a bit.
The documentation for the horizontal layout shows us how to use the theming attribute to give us some space between the components, with theme="spacing"
. The button also has themes, and setting it to primary
changes its color. To properly align the button and combo box vertically, set the horizontal layout to align-items: baseline
. Styles can be defined right in the template, if desired.
When the button is clicked, it should retrieve the selected item from the search box. The item should then be sent to the server to be saved, and the grid refreshed to reflect the changes.
Create a method onAddToList
without parameters, that is called when the button is clicked. Wire it up by adding @click=${this.onAddToList}
to the vaadin-button
element.
We get the selected item from the combo box through the selectedItem
property, and from there we get the symbol. If there is a symbol, we send it to our StockService
, and then update our grid.
There are a couple things to note here. It is possible that the stockSearchBox.selectedItem
is not set, therefore we use ?.
when referencing the symbol to avoid the JavaScript-equivalent of a null pointer exception. With JavaScript’s truthy values, if (symbol)
will be false if the value is null
, undefined
, or if it’s an empty string.
The addStock
method expects a Stock
as the argument. This is just an object that conforms to the Stock
structure: a key called symbol
with a string value, such as { symbol: 'AAPL' }
. As our variable is called the same as the would-be key, the key can be omitted.
After the stock has been added, the grid is updated. We do not use the result of the addStock
call, so we name the result variable _
, the shortest possible name that starts with an underscore.
We can now use the button to add stocks to our list. They are all saved on the server, but as they are in memory, they won’t survive a server restart.
The “Remove stock” button
Sometimes, we might not want to follow a stock any longer, so it would be convenient if we could remove it. Create a method for this, similar to the method for adding.
The stock will be removed by a button on each row of the grid, using a renderer. Define a method renderRemoveButton
that renders a vaadin-button
. When the button is clicked, onRemoveFromList
is called with the symbol extracted from the row data.
Instead of using a function reference for the click listener, we use a lambda, as we want to choose the argument that is passed.
There is currently no column using this renderer, so create one. We have one issue, though. The this
keyword inside a renderer will refer to the column it belongs to, not to the StockTracker
instance, which means this.onRemoveFromList
will not be resolved.
We work around that using the bind
method, with which we can bind the this
keyword to refer to our class instance. We save the bound method reference to a field, and set that as the renderer.
We could also bind the renderer directly in the property as
.renderer=${this.renderRemoveButton.bind(this)}
. This would, however, cause a new instance of the renderer function to be created each time the component is re-rendered.
Set text-align="end"
to align the button to the right. We can now remove stocks that aren’t up to par.
The finishing up
We have now reached the goals we set up: we can add a stock, remove a stock, and see information about a stock.
All apps need a title, so add one to the beginning of the template: <h1>StockTracker</h1>
. The font does not match the other components, as we have not imported the Lumo theme’s typography.
This is easily done in the ./frontend/index.html
file. After the existing <style>
tag, add the following tags
The typography ensures our title has the correct font. By importing the colors, we can add theme="dark"
to the <body>
element in the same file, to switch the Lumo theme from light to dark.
The app is now finished! And, as we are using a client-side view, the search box continues to work even when the server is offline. However, our current implementation does not allow us to add new stocks without it.
The complete code for this application can be found on GitHub.
Stock data provided for free by financialmodelingprep.com