Angular
alphaTab has no Angular wrapper: a component creates the AlphaTabApi on a view child in afterNextRender and destroys it with DestroyRef. This page shows the setup for Angular's default application builder and for its webpack builder.
npm install @coderline/alphatab
Application builderโ
Angular's default builder (@angular/build:application, esbuild) has no plugin API, bundles new Worker(new URL(...)) only in your own code, not inside dependencies, and has no Audio Worklet support. If alphaTab is imported and bundled normally, the production build still references ./alphaTab.worker.mjs next to main-[hash].js, the file does not exist, and the score never renders, without an error. ng serve hides the problem, because its Vite pre-bundling resolves the worker URL.
Keep alphaTab out of the bundle and let the browser load its ES modules instead. The page, the workers and the worklet then share one alphaTab.core.min.mjs. In angular.json, under projects.<app>.architect.build.options:
{
"externalDependencies": ["@coderline/alphatab"],
"index": {
"input": "src/index.html",
"preloadInitial": false
},
"assets": [
{ "glob": "**/*", "input": "public" },
{ "glob": "**/*", "input": "node_modules/@coderline/alphatab/dist/font", "output": "font" },
{ "glob": "sonivox.sf3", "input": "node_modules/@coderline/alphatab/dist/soundfont", "output": "soundfont" },
{
"glob": "alphaTab.{min.mjs,core.min.mjs,worker.min.mjs,worklet.min.mjs}",
"input": "node_modules/@coderline/alphatab/dist",
"output": "alphatab"
}
]
}
Map the package name to the copy with an import map in src/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="/">
<script type="importmap">{ "imports": { "@coderline/alphatab": "/alphatab/alphaTab.min.mjs" } }</script>
</head>
<body>
<app-root></app-root>
</body>
</html>
Create the component in src/app/app.ts:
import { afterNextRender, Component, DestroyRef, ElementRef, inject, signal, viewChild } from '@angular/core';
import * as alphaTab from '@coderline/alphatab';
@Component({
selector: 'app-root',
template: `
<button type="button" [disabled]="!playerReady()" (click)="playPause()">{{ playing() ? 'Pause' : 'Play' }}</button>
<div #alphaTab></div>
`
})
export class App {
private readonly element = viewChild.required<ElementRef<HTMLDivElement>>('alphaTab');
private api?: alphaTab.AlphaTabApi;
protected readonly playerReady = signal(false);
protected readonly playing = signal(false);
constructor() {
afterNextRender(() => {
const api = new alphaTab.AlphaTabApi(this.element().nativeElement, {
core: {
file: '/song.gp',
fontDirectory: '/font/'
},
player: {
enablePlayer: true,
enableCursor: true,
soundFont: '/soundfont/sonivox.sf3'
}
});
api.playerReady.on(() => this.playerReady.set(true));
api.playerStateChanged.on(e => this.playing.set(e.state === alphaTab.synth.PlayerState.Playing));
this.api = api;
});
inject(DestroyRef).onDestroy(() => this.api?.destroy());
}
protected playPause() {
this.api?.playPause();
}
}
externalDependencieskeepsimport ... from '@coderline/alphatab'as a bare import in the browser and the server bundle. TypeScript still checks against the installed package, and the initial bundle stays small enough for the default budgets.assetscopies alphaTab's minified ES modules to/alphatab/, the font to/font/and the SoundFont to/soundfont/.alphaTab.min.mjsimportsalphaTab.core.min.mjsand startsalphaTab.worker.min.mjsandalphaTab.worklet.min.mjsfrom next to itself, which import the same core. For debugging, copyalphaTab.{mjs,core.mjs,worker.mjs,worklet.mjs}and map the package to/alphatab/alphaTab.mjsinstead; each set references only its own files.preloadInitial: false. Otherwise the production build adds<link rel="modulepreload" href="@coderline/alphatab">, which browsers do not resolve through the import map; it fails with a MIME type error.afterNextRenderruns only in the browser, after hydration, so the component also works with server-side rendering. The server renders the button and an empty container. With Zone.js (the Angular 20 default), create alphaTab insideNgZone.runOutsideAngularso its timers and frequent events do not trigger change detection.
With server-side rendering:
- The server bundle imports
@coderline/alphatabfromnode_modulesat runtime, so deploy the server with its installed dependencies. - The files under
/alphatab/keep their names across alphaTab versions. The generatedserver.tsserves static files withmaxAge: '1y'; give/alphatab/a shorter cache lifetime or a versioned folder so browsers fetch new files after an update.
Samples: angular-22-esbuild (server-side rendering), angular-21-esbuild, angular-20-esbuild (Zone.js).
Alternativesโ
Two other setups work with the application builder, each loading the core more often:
| Setup | Core copies |
|---|---|
| alphaTab external with an import map (above) | 1 |
Bundle @coderline/alphatab normally and copy alphaTab.core.mjs, alphaTab.worker.mjs and alphaTab.worklet.mjs to the output root ("output": "/"); raise the initial budget to about 1.5 MB | 2 |
Own worker entry with import '@coderline/alphatab/worker', the app imports @coderline/alphatab/core and calls Environment.initializeMain; the worklet loads copied alphaTab.worklet.mjs and alphaTab.core.mjs | 3 |
Webpack builderโ
Projects that stay on Angular's webpack-based browser builder inject @coderline/alphatab-webpack with @angular-builders/custom-webpack. Angular deprecated its webpack support in version 22; use the application builder for new projects.
npm install @coderline/alphatab
npm install --save-dev @coderline/alphatab-webpack @angular-builders/custom-webpack @angular-devkit/build-angular
Point the build and serve targets in angular.json to the custom builder:
{
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": { "path": "./webpack.config.js" },
"outputPath": "dist/my-app",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [{ "glob": "**/*", "input": "public" }]
},
"configurations": {
"production": {
"budgets": [{ "type": "initial", "maximumWarning": "1.5MB", "maximumError": "2MB" }],
"outputHashing": "all"
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"configurations": {
"production": { "buildTarget": "my-app:build:production" },
"development": { "buildTarget": "my-app:build:development" }
},
"defaultConfiguration": "development"
}
}
Register the plugin in webpack.config.js and turn the shared core chunk off:
const { AlphaTabWebPackPlugin } = require('@coderline/alphatab-webpack');
module.exports = {
plugins: [new AlphaTabWebPackPlugin()],
optimization: {
splitChunks: {
cacheGroups: {
alphaTabCore: false
}
}
}
};
- The browser builder takes
main, notbrowser, as the entry option. alphaTabCore: falseis required. Angular's browser builder writes only its own named chunks (runtime,polyfills,main, โฆ) intoindex.html. The plugin's shared core chunk would never load, and the app would not start, showing a blank page without an error. Without the chunk, the core is bundled intomainand once more into a chunk the workers and the worklet share: two copies.- The plugin bundles the worker and the worklet although Angular turns webpack's worker parsing off; it re-enables it for alphaTab's modules only, and copies the font to
/font/and the SoundFont to/soundfont/. - Budgets.
maincontains alphaTab, about 1.4 MB raw and 280 kB transferred, so theinitialbudget is raised.
Use the component from the application builder section. Sample: angular-22-webpack.