How to Write a Simple Stock Tracker with Vaadin 15 Using Java and TypeScript

Posted 07 May 2020 by Erik Lumme

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.

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 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.

Our stock-tracker web component inserted into the DOM
Our stock-tracker web component inserted into the DOM

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 default stocks as logged in the browser console
The default stocks as logged in the browser console

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, setting noUnusedLocals 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 grid showing our default stocks
The grid showing 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,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 result of the FMP API call being logged to the browser console
The result of the FMP API call being logged to the browser console

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 updated grid
The updated grid

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!

The styled percentage change column
The styled percentage change column

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.

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<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.

The search box with sub-par presentation
The search box with sub-par presentation

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.

The search box with correct presentation
The search box with correct presentation

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 formatted combo box items
The formatted combo box items

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.

Things are looking neater
Things are looking neater

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.

It's a good day for stocks
It's a good day for stocks

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.

Buttons for removing a stock
Buttons for removing a stock

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