Angular on Steroids: Elevating Performance with WebAssembly
Tags: angular, webassembly, typescript, tutorial
Published: 2023-12-25 08:34:53

##Introduction

In this blog post, I demonstrated how to use WebAssembly within an Angular application easily. In some cases, an Angular application wants to perform a task that is not fast in JavaScript.  Developers can rewrite the algorithm in other languages such as AssemblyScript and Rust to write efficient codes.  Then, the developers can compile the codes to WASM  file, and stream the binary in the application to call the WASM functions. It is also possible that developers cannot find open source libraries in the NPM registry for the job. They have the option to write new package in non-JS languages, compile it into WASM and publish the WASM codes to NPM registry.  Angular developers install the new package as dependency and execute the WASM functions within an application. 

In the following demo, I wrote some prime number functions in AssemblyScript and published the index file into a WASM file. Then, I copied the WASM file to Angular application, streamed the binary with WebAssembly API and finally called these functions to perform various actions related to prime numbers.  

###What is WebAssembly?

WebAssembly can break down into 2 words: Web and Assembly.  High level programming languages such as AssemblyScript and Rust write codes that are compiled into **assembly** by tools. Then, developers run the assembly codes natively on browser in the **web**. 

###Use case of the demo

This demo has 2 github repositories:  The first repository uses AssemblyScript to write TypeScript-like codes that compile into Wasm.  The second repository is a simple Angular application that uses the Wasm functions to explore some interesting facts of prime numbers  

In the AssemblyScript repository, the index file has 3 prime number functions:

- isPrime - Determine whether or not an integer is a prime number
- findFirstNPrimes - Find the first N prime numbers where N is an integer
- optimizedSieve - Find all the prime numbers less than N where N is an integer

AssemblyScript adds scripts in package.json that generate **debug.wasm** and **release.wasm** respectively.

I copied **release.wasm** to `assets` folder of the Angular application, wrote a WebAssembly loader to stream the binary file and return a WebAssembly instance.  The main component bound the instance to the components as input, and these components used the instance to execute Wasm and utility functions to obtain prime number results.

###Write WebAssembly in AssemblyScript

AssemblyScript is a TypeScript-like language that can write codes to compile into WebAssembly.

####Start a new project

```bash
npm init
```

####Install dependency

```bash
npm install --save-dev assemblyscript
```

####Run command to add scripts in package.json and scaffold files

```bash
npx asinit .
```

####Custom scripts to generate debug.wasm and release.wasm files

```bash
"scripts": {
    "asbuild:debug": "asc assembly/index.ts --target debug --exportRuntime",
    "asbuild:release": "asc assembly/index.ts --target release --exportRuntime",
    "asbuild": "npm run asbuild:debug && npm run asbuild:release",
    "start": "npx serve ."
}
```

####Implement prime number algorithm in AssemblyScript

```typescript
// assembly/index.ts

// The entry file of your WebAssembly module.

// module import
declare function primeNumberLog(primeNumber: i32): void;

export function isPrime(n: i32): bool {
  if (n <= 1) {
    return false;
  } else if (n === 2 || n === 3) {
    return true;
  } else if (n % 2 === 0 || n % 3 === 0) {
    return false;
  }
  
  for (let i = 5; i <= Math.sqrt(n); i = i + 6) {
    if (n % i === 0 || n % (i + 2) === 0) {
      return false;
    }
  }

  return true;
} 

export function findFirstNPrimes(n: i32): Array<i32> {
  let primes = new Array<i32>(n);

  for (let i = 0; i < n; i++) {
    primes[i] = 0;
  }
  primes[0] = 2;
  primeNumberLog(primes[0]);

  let num = 3;
  let index = 0;
  while(index < n - 1) {
    let isPrime = true;

    for (let i = 0; i <= index; i++) {
      if (num % primes[i] === 0) {
        isPrime = false;
        break;
      }
    }

    if (isPrime) {
      primeNumberLog(num);
      primes[index + 1] = num;
      index = index + 1;
    }
    num = num + 2;
  }

  return primes;
}

const MAX_SIZE = 1000001;
 
export function optimizedSieve(n: i32): Array<i32> {
  const isPrime = new Array<bool>(MAX_SIZE);
  isPrime.fill(true, 0, MAX_SIZE);

  const primes = new Array<i32>();
  const smallestPrimeFactors = new Array<i32>(MAX_SIZE);
  smallestPrimeFactors.fill(1, 0, MAX_SIZE);

  isPrime[0] = false;
  isPrime[1] = false;

  for (let i = 2; i < n; i++) {
    if (isPrime[i]) {
      primes.push(i);

      smallestPrimeFactors[i] = i;
    }

    for (let j = 0; j < primes.length && i * primes[j] < n && primes[j] <= smallestPrimeFactors[i]; j++) {
      const nonPrime = i * primes[j];
      isPrime[nonPrime] = false;
      smallestPrimeFactors[nonPrime] = primes[j];
    }
  }

  const results = new Array<i32>();
  for (let i = 0; i < primes.length && primes[i] <= n; i++) {
    results.push(primes[i]);
  }

  return results;
}
```

`primeNumberLog` is an external function that logs prime numbers in `findFirstNPrimes`. The function has no body and Angular application is responsible for providing the implementation details.

After executing `npm run asbuild` script, the `builds/` folder contains `debug.wasm` and `release.wasm`.  The part with WebAssembly is done and I proceeded with Angular application.

###Combine the power of WebAssembly and Angular 

WebAssembly does not transfer high-level data types such as array and boolean. Therefore, I installed assemblyscript loader and applied its utility functions to convert the returned values of Wasm functions to the correct type.

####Install dependency

```bash
npm i @assemblyscript/loader
```

####Build a WebAssembly loader

After trials and errors, the Angular application was able to import the Wasm functions by streaming `release.wasm` with a assemblyscript loader.

```bash
src
├── assets
 │   └── release.wasm
├── favicon.ico
├── index.html
├── main.ts
└── styles.scss
```

I encapsulated the loader in a WebAssembly loader service such that all Angular components can reuse the streaming functionality. If browser supports `instantiateStreaming` function, a WebAssembly instance is returned.  If `instantiateStreaming` function is unsupported, the fallback will be called.  The fallback converts the response to an array buffer and constructs a WebAssembly instance.  

`DEFAULT_IMPORTS` also supplies the implementation of primeNumberLog.  primeNumberLog is declared in index.ts of the AssemblyScript repository; therefore, the object key is index without the file extension.

```typescript
// web-assembly-loader.service.ts

import { Injectable } from '@angular/core';
import loader, { Imports } from '@assemblyscript/loader';

const DEFAULT_IMPORTS: Imports = { 
  env: {
    abort: function() {
      throw new Error('Abort called from wasm file');
    },
  },
  index: {
    primeNumberLog: function(primeNumber: number) {
      console.log(`primeNumberLog: ${primeNumber}`);
    }
  }
}

@Injectable({
  providedIn: 'root'
})
export class WebAssemblyLoaderService {
  async streamWasm(wasm: string, imports = DEFAULT_IMPORTS): Promise<any> {
    if (!loader.instantiateStreaming) {
      return this.wasmFallback(wasm, imports);
    }

    const instance = await loader.instantiateStreaming(fetch(wasm), imports);
    return instance?.exports;
  }

  async wasmFallback(wasm: string, imports: Imports) {
    console.log('using fallback');
    const response = await fetch(wasm);
    const bytes = await response?.arrayBuffer();
    const { instance } = await loader.instantiate(bytes, imports);

    return instance?.exports;
  }
}
```

####Bind WebAssembly instance to Angular Components

In AppComponent, I streamed release.wasm to construct a WebAssembly instance. Then, I bound the instance to the input of the Angular Components.

```typescript
// app.config.ts

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_BASE_HREF,
      useFactory: () => inject(PlatformLocation).getBaseHrefFromDOM(),
    }
  ]
};
```

```typescript
// full-asset-path.ts

export const getFullAssetPath = (assetName: string) => {
    const baseHref = inject(APP_BASE_HREF);
    const isEndWithSlash = baseHref.endsWith('/');
    return `${baseHref}${isEndWithSlash ? '' : '/'}assets/${assetName}`;
}
```

```typescript
// app.component.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [FormsModule, IsPrimeComponent, FindFirstNPrimesComponent, OptimizedSieveComponent],
  template: `
    <div class="container outer" style="margin: 0.5rem;">
      <h2>Angular + WebAssembly Demo</h2>
      <app-is-prime [instance]="instance" />
      <app-find-first-nprimes [instance]="instance" />
      <app-optimized-sieve [instance]="instance" />
    </div>
  `,
})
export class AppComponent implements OnInit {
  instance!: any;
  releaseWasm = getFullAssetPath('release.wasm');
  wasmLoader = inject(WebAssemblyLoaderService);

  async ngOnInit(): Promise<void> {
    this.instance = await this.wasmLoader.streamWasm(this.releaseWasm);
    console.log(this.instance);
  }
}
```

####Apply WebAssembly to Angular Components

`IsPrimeComponent` invokes `isPrime` function to determine whether or not an integer is a prime number.  `isPrime` returns 1 when it is a prime number and 0, otherwise. Therefore, === operator compares the integer values to return a boolean.

```typescript
// is-prime.component.ts

@Component({
  selector: 'app-is-prime',
  standalone: true,
  imports: [FormsModule],
  template: `
    <form>
      <label for="primeNumber">
        <span>Input an positive integer: </span>
        <input id="primeNumber" name="primeNumber" type="number"
          [ngModel]="primeNumber()" (ngModelChange)="primeNumber.set($event)" />
      </label>
    </form>
    <p class="bottom-margin">isPrime({{ primeNumber() }}): {{ isPrimeNumber() }}</p>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IsPrimeComponent {
  @Input({ required: true })
  instance!: any;

  primeNumber = signal(0);
  
  isPrimeNumber = computed(() => { 
    const value = this.primeNumber();
    return this.instance ? this.instance.isPrime(value) === 1 : false
  });
}
```

`FindFirstNPrimesComponent` invokes `findFirstNPrimes` function to obtain the first N prime numbers.  `findFirstNPrimes` cannot transfer integer array; therefore, I apply `__getArray` utility function of the loader to convert the integer value to correct integer array.

```typescript
// find-first-nprimes.component.ts

@Component({
  selector: 'app-find-first-nprimes',
  standalone: true,
  imports: [FormsModule],
  template: `
    <form>
      <label for="firstNPrimeNumbers">
        <span>Find first N prime numbers: </span>
        <input id="firstNPrimeNumbers" name="firstNPrimeNumbers" type="number"
          [ngModel]="firstN()" (ngModelChange)="firstN.set($event)" />
      </label>
    </form>

    <p class="bottom-margin">First {{ firstN() }} prime numbers:</p>
    <div class="container first-n-prime-numbers bottom-margin">
      @for(primeNumber of firstNPrimeNumbers(); track primeNumber) {
        <span style="padding: 0.25rem;">{{ primeNumber }}</span>
      }
    <div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FindFirstNPrimesComponent {
  @Input({ required: true })
  instance!: any;

  firstN = signal(0);

  firstNPrimeNumbers = computed(() => {
    const value = this.firstN();
    if (this.instance) {
      const { findFirstNPrimes, __getArray: getArray } = this.instance;
      return getArray(findFirstNPrimes(value));
    }

    return [];
  });
}
```

`OptimizedSieveComponent` invokes `optimizedSieve` function to obtain all prime numbers that are less than N.  Similarly, `optimizedSieve` cannot transfer integer array and  I apply `__getArray` utility function to convert the integer value to correct integer array.

```typescript
// optimized-sieve.component.ts

@Component({
  selector: 'app-optimized-sieve',
  standalone: true,
  imports: [FormsModule],
  template: `
    <form>
      <label for="primeNumber">
        <span>Input an positive integer: </span>
        <input id="primeNumber" name="primeNumber" type="number"
          [ngModel]="lessThanNumber()" (ngModelChange)="lessThanNumber.set($event)" />
      </label>
    </form>

    <p class="bottom-margin">Prime numbers less than {{ lessThanNumber() }}</p>
    <div class="container prime-numbers-less-than-n bottom-margin">
      @for(primeNumber of primeNumbers(); track primeNumber) {
        <span style="padding: 0.25rem;">{{ primeNumber }}</span>
      }
    <div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OptimizedSieveComponent {
  @Input({ required: true })
  instance!: any;

  lessThanNumber = signal(0);
  
  primeNumbers = computed(() => { 
    const value = this.lessThanNumber();
    if (this.instance) {
      const { optimizedSieve, __getArray: getArray } = this.instance;
      return getArray(optimizedSieve(value));
    }

    return [];
  });
}
```

The following Github page shows the final results:
{%embed https://railsstudent.github.io/ng-webassembly-demo/ %}

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.

##Resources:

- Github Repo of WebAssembly: https://github.com/railsstudent/prime-number-wasm
- Github Repo of Angular + WebAssembly demo:  https://github.com/railsstudent/ng-webassembly-demo
- Live demo: https://railsstudent.github.io/ng-webassembly-demo/
- AssemblyScript:  https://angular.dev/guide/templates/control-flow#if-block-conditionals
- WebAssembly API: https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiateStreaming_static