Observable Web Workers with Angular (8) - Introduction
Tags: angular, webworker, observables, rxjs
Published: 2019-06-16 21:28:54

---
title: "Observable Web Workers with Angular (8) - Introduction"
published: true
cover_image: https://thepracticaldev.s3.amazonaws.com/i/59lw8ftqf8y4d78yfdxi.jpg
description: "Introduction to `observable-webworker`, a library to help make super clean streaming web worker implementations"
tags: angular, webworker, observables, rxjs
series: observable-webworkers
---

## tl;dr
Web Workers are awesome and Angular CLI now supports them natively.  
Unfortunately, the Web Worker API is not Angular-like and therefore, I'm introducing a library [`observable-webworker`](https://www.npmjs.com/package/observable-webworker)

If you already know what web workers are and how to use them in Angular, feel free to [skip ahead](#implementing-observablewebworker) where I will show you how to refactor the initial worker generated by the CLI into a superpowered worker with `observable-webworker`.

## Context
In this article series we will explore using [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to offload heavy computation to another thread. Keeping the main thread free for interaction with the user is important. We don't typically think of threading very often in the frontend world, as non-blocking APIs and the event loop model of Javascript typically allows us to have execution of user interaction events _in-between_ longer running processes such as waiting for an HTTP request to resolve.

Because of this, for the most part, you don't need threading or web workers for most tasks. However, there are a set of problems that do require heavy computation that would ordinarily block the main thread, and therefore user interaction (or modification of the DOM). This issue can manifest itself in stuttering animations, unresponsive inputs, buttons that appear not to work immediately etc.

Often the answer to this has been to run intensive computations server side, then send the result back to the browser once done. This solution does have a real cost however - you need to manage a server API, monetarily computation itself isn't free (you're paying for the server) and there may be a significant latency issue if you need to interact with the computation frequently.

Fortunately, `Web Workers` are the solution to all this - they allow you to schedule units of work in browser that run _in parallel_ to the main execution context, then once done can pass their result back to the main thread for rendering in the DOM etc.

## Creating your first Web Worker in Angular
The worker API is relatively simple - in Typescript you can create a new worker with just
<!-- embedme src/readme/basic-worker.ts --> 
```ts
const myWorker = new Worker('./worker.js');

```

This is all very well, however we love to use Typescript, and be able to import other files etc. Being stuck with a single javascript file is not very scalable.

Previously working with workers (heh) in Angular was fairly painful as bundling them requires custom Webpack configuration. However, Angular CLI version 8 brings built-in support for properly compiling & bundling web workers.

The docs are at https://angular.io/guide/web-worker but we will step through everything required (and more!) here.


Before we get going, we've set up an empty Angular CLI project with

```bash
ng new observable-workers
```

Next we need to enable web worker support, so we run 
```
ng generate web-worker
```
We're prompted for a name, so we will call it demo.

Angular CLI will now update `tsconfig.app.json` and `angular.json` to enable web worker support, and create us a new `demo.worker.ts`:

<!-- embedme src/readme/initial-angular-cli-worker/demo.worker.ts -->
```ts
/// <reference lib="webworker" />

addEventListener('message', ({ data }) => {
  const response = `worker response to ${data}`;
  postMessage(response);
});

```

To get this worker running, lets update our `AppComponent`:
<!-- embedme src/readme/initial-angular-cli-worker/app.component.ts -->
```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  runWorker() {

    const demoWorker = new Worker('./demo.worker', { type: 'module'});

    demoWorker.onmessage = (message) => {
      console.log(`Got message`, message.data);
    };

    demoWorker.postMessage('hello');

  }

}

```

And the template:

<!-- embedme src/readme/initial-angular-cli-worker/app.component.html -->
```html
<button (click)="runWorker()">Run Worker</button>

```

Okay, so now we're all good to go, run the application if you aren't already, and you'll be greeted with a single button in the DOM. Are you excited yet?

Click that sucker, and you will see in the dev console

```
Got message worker response to hello
```

Yeehaw, we got a response from a separate thread, and **Angular compiled the worker for us**.

Also, to prove this is actually a worker, if you go to the `Sources` tab in Chrome, you will see that there is a worker running there as `1.worker.js`.

The fact that we can still see it running is important - it means that we have not destroyed the worker, despite receiving the only message we will get back from it. If we click the button again, we will construct a brand new worker, and the old one will continue to hang around. This is a bad idea! We should always clean up after we're done with a worker.

Before we worry about how to destroy a worker, let's reflect for a bit on the API that we have with workers so far - we need to:
- construct the worker  
- declare a property and assign a function to it in order to get the response back from the worker
- `addEventListener` within the worker itself, we have to
- call a global `postMessage` function to send back information to the main thread

This API doesn't feel very Angular-like does it? We're used to deal with clean hook-based APIs and have fallen in love with RxJS for dealing with streams of data and asynchronous responses, why can't we have this for Web Workers too?

Well, we can. And this happens to be the whole point of this article. I'd like to introduce a new library [`observable-webworker`](https://www.npmjs.com/package/observable-webworker) which seeks to address this clunky API and give us a familiar experience we're used to with Angular.

I should note that this library doesn't actually depend on Angular at all, and will work beautifully with React or any other framework or lack thereof! The only dependency is a `peerDependency` on RxJS.

 To best introduce the concepts of the library, we will refactor our current web worker to use `observable-webworker`.
 
## Implementing observable-webworker
 
 To start, we will install `observable-webworker`. I'm using Yarn, but you know how to install packages right?!
 
 ```bash
 yarn add -E observable-webworker
 ```
 
First of all, we'll update the `AppComponent`
 <!-- embedme src/readme/observable-webworker-implementation/app.component.ts -->
```ts
import { Component } from '@angular/core';
import { fromWorker } from 'observable-webworker';
import { Observable, of } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  runWorker() {

    const input$: Observable<string> = of('hello');

    fromWorker<string, string>(() => new Worker('./demo.worker', { type: 'module'}), input$)
      .subscribe(message => {
        console.log(`Got message`, message);
      });

  }

}

```

Note we've imported `fromWorker` from `observable-webworker`. The first argument is a worker _factory_ - we need to have the ability to lazily construct a worker on-demand, so we pass a factory and the library can construct it when needed. Also Webpack needs to find the `new Worker('./path/to/file.worker')` in the code in order to be able to bundle it for us.
The second argument `input$` is a simple stream of messages that will go to the worker. The generics (`<string, string>`) that we pass to the worker indicate the input and output types. In our case the input is a very simple `of('hello')`.

Now for the worker:
 
<!-- embedme src/readme/observable-webworker-implementation/demo.worker.ts -->
```ts
import { DoWork, ObservableWorker } from 'observable-webworker';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@ObservableWorker()
export class DemoWorker implements DoWork<string, string> {

  public work(input$: Observable<string>): Observable<string> {

    return input$.pipe(
      map(data => `worker response to ${data}`)
    );
  }

}

```

You can see we have completely restructured the worker to use an API that is far more familiar to Angular developers - we use a decorator `@ObservableWorker()` which lets the library bootstrap and work its magic, and we implement the `DoWork` interface, which requires us to create a `public work(input$: Observable<string>): Observable<string>;` method.

The actual content of the method is super simple. We process the observable stream with a simple `map`, which is returned from the method. Behind the scenes the library will call `postMessage` to get the data back to the main thread.

You can now run the application again and it will work exactly as before, with the one exception that the worker will automatically be terminated as no more work needs to be done.

As we're now using observables, the library is able to know when subscribers have finished listening and can clean up the worker appropriately.

This wraps up Part One of this series. In later articles we will discuss a more real-world implementation of workers, how to deal with errors, and how to work with very large object passing between main and worker threads. Possibly I will also do a deep dive into the implementation of observable-webworker as it uses some of the more exotic RxJS operators like `materialize()` and `dematerialize()`.

All of these features are available with `observable-webworker` now, so I encourage you to [check out the readme](https://github.com/cloudnc/observable-webworker#observablewebworker), as it goes into more detail about the features than I do here in this article.

---

This is my first blog post ever! I'd love to hear from you if you have any feedback at all, good or bad!