Lukas Marx
April 19, 2018
Angular

Using Web Assembly to speed up your Angular Application

For some applications, JavaScript just isn't fast enough...

But there is hope!

Web Assembly is faster than JavaScript and can run in the most popular browsers today!

In this tutorial, we are going to take a look at Web Assembly in angular.

We will discover how we can compile any C program into Web Assembly and the use inside of a simple angular service to speed things up!

Ready?

Let's get started!

angular-question-wasm

What is Web Assembly?

Web Assembly is a binary instruction format. It is designed to have a very small file-size due to its binary format.

The main advantage of Web Assembly that it is fast. (Almost) As fast as a native program.

But why is this important?

JavaScript is slow!

Compared with native programs written in C or C++ JavaScript is very slow. This is because it is not directly translated into processor instructions, but runs in a virtual machine instead.

This gives the developer some great features like automatic garbage collection.

That means the developer has not to allocate and free up system memory himself. As the past has shown, this process can be very complex and easy to get wrong. So not having to worry about memory allocation is actually a good thing.

But this convenience comes at a cost. The additional layer of the virtual machine costs JavaScript quite a bit of performance.

Furthermore, JavaScript is not a compiled language. That means, that the code has to be compiled at runtime to be executed. Because this process is typically relatively slow, JavaScript uses a technique called Just-In-Time compilation (JIT). Although JIT reduces the load-time of the program a lot, it is not as fast a executing pre-compiled programs.

Web Assembly to the rescue!

No, seriously, JavaScript is just fine for most web applications. It is easy to use and especially with modern frameworks quite fast.

But for certain applications it is not fast enough.

These are typically applications that require a lot of calculations.

That is where Web Assembly shines. Examples for that are applications like image manipulations or games.

Web Assembly First Draft

The current version of Web Assembly is still quite limited compared to what features a promised. In fact, it is just a fist draft, to establish the technology and get people excited about it.

Despite of its early stage, Web Assembly is currently supported by Firefox, Chrome, Edge and Safari.

angular-code-wasm

A C-Program in Angular

Web Assembly can be generated from many languages such as C, C++ or Rust. There is even an experimental project that compiles TypeScript to Web Assembly.

For this tutorial, we will keep it simple and compile a very basic C program.

Set Up a new Angular Project

Before we get started, we nee to set up a new project.

We can use the angular-cli to do so:

ng new angular-wasm

We also need the typings for the Web Assembly JavaScript API. Let's install them:

npm install @types/webassembly-js-api --dev --save

Installing the Web Assembly Compiler

To install the Web Assembly compiler, please follow these instructions: Web Assembly Getting Started.

If you are on Windows, you might have to add the path of the emsdk to you PATH variables.

angular-compile-wasm

Compiling C to WASM

Before we compile anything, we need to create our C program first.

To do that, please go ahead and create a folder called "wasm" inside of your project root. Inside of that folder, create a file called "fibonacci.c".

That's right! Our example will calculate the Fibonacci number of any number, because that is quite an expensive task. Especially for the higher numbers.

The C Programm

The C program looks like this:

int fibonacci(int n)
{
    if (n == 0 || n == 1)
        return n;
    else
        return (fibonacci(n - 1) + fibonacci(n - 2));
}

Even if you are not familiar with C programming, this program should be quite easy to understand.

Exposing the Function to JavaScript

What we want to do here, is to make the Fibonacci function callable from our JavaScript code.

Therefore, we need to mark it with a special decorator. For this decorator, we need to include emscripten.h.

Afterwards, the program looks like this:

#include <emscripten.h>

int EMSCRIPTEN_KEEPALIVE fibonacci(int n)
{
    if (n == 0 || n == 1)
        return n;
    else
        return (fibonacci(n - 1) + fibonacci(n - 2));
}

Compiling C to Web Assembly (WASM)

To use this function, we need to compile it to Web Assembly first, because the browser does not understand C.

To do that, we are using the compiler we have installed before.

The command to do so looks like this:

emcc wasm/fibonacci.c -Os -s WASM=1 -s MODULARIZE=1 -o wasm/fibonacci.js

The -Os options defines the grade of optimizations performed. We use a quite high grade here.

Besides WASM, the compiler will also generate a JavaScript file. This file includes some glue-code, that handles the communication between WASM and JavaScript. With the MODULARIZE=1 option we tell the compiler to wrap that code into a module. That way it is much easier to consume in our angular app.

The result of that command should be two new files: fibonacci.js and fibonacci.wasm.

angular-service-wasm

Wrapping Web Assembly in an Angular Service

Now we can use our WASM function in angular. The best place for these utility-style of functions are in a separate service. So we are going to create a new service called WasmService.

To let the angular-cli generate that service for us, use this command:

ng generate service wasm

Unfortunately, using WASM modules is not as quite as easy as using plain JavaScript ones.

We do not only have to import our WASM JavaScript glue-code module

src/app/wasm.service.ts
import * as Module from './../../wasm/fibonacci.js'

but also have to import the was file itself using the file-loader

import '!!file-loader?name=wasm/fibonacci.wasm!../../wasm/fibonacci.wasm';

Unfortunately, that way the WASM file is not included in the bundle itself, but is provided as separate file.

Instantiate Web Assembly

To use the WASM-file at runtime, it has to be fetched from the provided URL and converted into a byte array.

To do that, we create a new method inside of our service called "instantiateWasm".

src/app/wasm.service.ts
private async instantiateWasm(url: string){
  // fetch the wasm file
  const wasmFile = await fetch(url);

  // convert it into a binary array
  const buffer = await wasmFile.arrayBuffer();
  const binary = new Uint8Array(buffer);

  // create module arguments
  // including the wasm-file
  const moduleArgs = {
    wasmBinary: binary,
    onRuntimeInitialized: () => {
      // TODO
    }
  };

  // instantiate the module
  this.module = Module(moduleArgs);
}

Notice that we also need to create a property on the service called "module". This property will contain the module, with all the functions that it has, including our Fibonacci function.

We can now call that method in the constructor of our service:

src/app/wasm.service.ts
constructor() {
  this.instantiateWasm('wasm/fibonacci.wasm');
}

To provide that Fibonacci function to our angular components in a convenient way, we are going to create a method with the same name in our service.

Inside of that method, we are just calling the WASM function.

src/app/wasm.service.ts
public fibonacci(input: number): number{
 return this.module._fibonacci(input)
}

Notice that all functions that are actually WASM functions start with an underscore.

angular-delay-wasm

Delaying Functions until Web Assembly is loaded

That's it! We can now use the service just as any other angular service.

But there is a problem!

Our instantiateWasm method is async and actually takes a while to load the Web Assembly module. When someone calls our Fibonacci method in the meantime, the module property is still undefined.

To solve that problem, we change our Fibonacci mehtod to return an observable. With the help of the observable, we can delay the execution of the method until our service is ready.

Furthermore, we need to create a BehaviorSubject in our service.

src/app/wasm.service.ts
wasmReady = new BehaviorSubject<boolean>(false)

We update this Subject, once our service is ready.

src/app/wasm.service.ts
// update this in instantiateWasm()
const moduleArgs = {
  wasmBinary: binary,
  onRuntimeInitialized: () => {
    this.wasmReady.next(true) // <-- this line
  },
}

We then filter that observable in our Fibonacci method to only continue when the value of the subject is true. For that, we use the filter mehtod from rxjs. We are also going to need the take and the mergeMap operator.

import { filter} from 'rxjs/operators';

The actual pipeline then looks like this:

src/app/wasm.service.ts
public fibonacci(input: number): Observable<number> {
    return this.wasmReady.pipe(filter(value => value === true)).pipe(
      map(() => {
        return this.module._fibonacci(input);
      })
    );
  }

The Full WASM Service Code

Here is what the service should look like once you are done:

src/app/wasm.service.ts
import { Injectable } from '@angular/core'
import { Observable, BehaviorSubject } from 'rxjs'
import { filter, map } from 'rxjs/operators'

import * as Module from './../../wasm/fibonacci.js'
import '!!file-loader?name=wasm/fibonacci.wasm!../../wasm/fibonacci.wasm'

@Injectable()
export class WasmService {
  module: any

  wasmReady = new BehaviorSubject<boolean>(false)

  constructor() {
    this.instantiateWasm('wasm/fibonacci.wasm')
  }

  private async instantiateWasm(url: string) {
    // fetch the wasm file
    const wasmFile = await fetch(url)

    // convert it into a binary array
    const buffer = await wasmFile.arrayBuffer()
    const binary = new Uint8Array(buffer)

    // create module arguments
    // including the wasm-file
    const moduleArgs = {
      wasmBinary: binary,
      onRuntimeInitialized: () => {
        this.wasmReady.next(true)
      },
    }

    // instantiate the module
    this.module = Module(moduleArgs)
  }

  public fibonacci(input: number): Observable<number> {
    return this.wasmReady.pipe(filter(value => value === true)).pipe(
      map(() => {
        return this.module._fibonacci(input)
      })
    )
  }
}

Conclusion

In this tutorial we discovered, how we can use the power of Web Assembly in our angular application, by wrapping the Web Assembly API into a simple service.

I hope you liked this article. If you did, please share it with your friends!

You can find the full source code at the corresponding GitHub repository.

Happy Coding!


Leave a comment

We save your email address, your name and your profile picture on our servers when you sing in. Read more in our Privacy Policy.