10 minutes read
In this installment we’ll write an app that retrieves data from a remote OData service and displays it in a data table. But unlike most of web apps that depend on some kind of external storage we’re not going to write the query-logic inside the app itself. Instead, we’ll outsource the data retrieval part into a separate TypeScript file that’ll constitute our WebWorker. This file will be loaded in a context separated from ‘window’ context. Our UI will be based on the nice jquery.dataTables library and just to make it somewhat more realistic we’ll implement another component, Console, for event logging.
Our app’s structure and data flow look like this:
And this is the final app:
Here’s a live demo for testing.
What are WebWorkers?
At the first sight the first component, MyAppComponent, looks like a mere container containing the other two. But there’s more as we want to instantiate the WebWorker outside of any specialized components. Additionally, we’d like to have the event handling and message passing being centralized inside a single component. Therefore we decide to instantiate our web worker within the MyAppComponent. Also we provide two event handlers.
The heart of the whole is located in the initWebWorker method because there we instantiate the logic residing in the external worker.js script. This script must be located within a path known by the web server. Together with the instantiation we take care of providing a proper listener to message events. In our case we decide to take the data-property reference because we know that all data provided by WebWorkers will always be copied when leaving its execution context. Additionally, we want to maintain a log of such events by writing a short entry into another property, lastEntry. Finally, we send a signal to Angular’s ChangeDetector to update this component and all of its children. Now let’s examine our component’s template to understand the data flow.
Ignoring the ubiquitous Bootstrap UI library I’m using here the core of our template is based on two special tags: customers and console (Lines 4 and 7) and a button on line 12.
Let’s first dissect the customers-tag:
Our logging component, Console, doesn’t emit any events up the chain but expects a single value to be provided by its parent component:
The third player in this game is the button we use to initialize the execution of the retrieval logic that’ll query a remote OData service, then pass a message from the Worker to our component and finally land as a set of rows inside the table.
To activate the whole logic we use the standard click event and wire it up to the onGetCustomersClicked handler from MyAppComponent. Inside this method we find a single command, a postMessage call of the Worker we instantiated previously. Of course, the message we’re using here is just a demo without any complex semantics behind. WebWorkers and their postMessage methods are very flexible as they allow us to define APIs that react to any kind of commands, messages, arguments etc. We’re completely free here to define our API of choice and indeed, one should take some time to think about the definition of a proper messaging API before developing a WebWorker.
Now let’s examine our WebWorker’s source code. As already described, the worker must live outside the thread of our main application and will only react to messages passed via standardized interfaces. As the standard says we must provide a proper event handler for message events. Here we’re using TypeScript, just like with the rest of our code, to make ourselves more happy (strong typing!) but when it comes to postMessage we must resort to some weird casting because a method with the same name is also a part of our browser’s interface. Of course, we know that our WebWorker would never get a chance to do anything with the DOM and there’s no sane logic to even try to map our postMessage to the one from the browser. However, there’s still a problem with this collision as the TypeScript transpiler would run into errors when trying to produce a working JS-script. Therefore, we cast the current object (WebWorker) into any, call the method postMessage on it and pass our result as its argument. Now TypeScript will correctly transpile the Worker’s code.
Although a simple demo our worker executes a slightly more complex task than just passing some messages back and forth. We’re accessing a remote OData service and transforming retrieved values into tabular data. To make it easier to control the logic we’re outsourcing it into another script called nw.client. There we’re using a very flexible library called o.js that does all the tedious and error prone stuff regarding OData access, parsing, filtering etc.
NwClient is a class containing a private setup method and a single public interface get() that expects a table name as its sole argument. It returns an Observable of values containing a data property where our expected results should be located. The setupOData method is based on the tutorial from o.js GitHub pages. More detailed info on o.js and how to use it can be found here. All that our client does is effectively taking a table name and querying the service whose URL is hard-coded in a variable above the class. But it’s also possible to define other OData services by using the NwClient’s constructor.
Retrieving data and displaying it
Now, what happens to the data we get from the service? First, the main component, MyAppComponent, reacts to WebWorker’s message event and copies the data to its own property, customersList . Then it forwards the data to its subordinated child components as we saw it in MyAppComponent’s template definition. There, in CustomersComponent, the data goes a step further and gets parsed into columns by the table component.
Angular’s OnChanges lifecycle hook is responsible for refreshing the table. Each time the main component changes the properties of its child components the life cycle method ngOnChanges will be called. We refresh the table because we know that our main component only changes properties after having received a message from the WebWorker. Of course one thing’s still missing: our table’s definition. This is how it looks like when we define a data table that’s based on jquery.dataTables plugin. The most important part is the data property that receives the reference to the list property that’s decorated with @Input().
Regarding tabular data, there’s no reason not to use any other widget library or table, so I’ll leave it to the reader to decide if he/she prefers this or any other table. I like jquery.dataTables because it’s easy to use, it’s open-source and isn’t a part of some bigger widget framework.
Our table is capable of emitting events when users click on rows. The value provided by rowSelected, which is of type EventEmitter, contains data from the row that was clicked. This data will be sent up the chain to the parent component that’ll use it to populate the message property which belongs to ConsoleComponent.
Again, we’re utilizing the life cycle method OnChanges to react to incoming data from our parent component. And just to make our UI more user friendly we constrain the amount of visible messages to 10. And as with any other components that are capable of receiving external data, we use @Input() annotation to declare the property message as a data-bound property that’ll be changed during change detection.
Building the App, building the WebWorker
WebWorkers are an alternative way to separate heavy-weight chunks of code from the main thread (mostly UI). They can help us overcome some limitations of our browser’s environments which often result in slow UIs and ugly pauses between refreshes. Dealing with WebWorkers looks easy because of its rather simple API structure. However, deploying them and keeping everything in sync takes some time and I hope this article will be of some value to you.
Have fun! 🙂