This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Integrations

Use AngularTS from JVM, JavaScript-targeting, native, and WebAssembly languages.

AngularTS modules, injection, components, directives, and scopes are available from several languages. Choose the language that already builds the part of your application that runs on the page.

Begin with the integration model, then open the setup guide for your language.

Java, ClojureScript, and Scala.js are available from Maven Central. Dart is on pub.dev, Gleam is on Hex, and Closure externs ship in the npm package. Kotlin and the WebAssembly bindings are currently distributed as source with complete examples.

Native platforms

JavaScript and JVM languages

WebAssembly languages

1 - Choose an integration

Choose a language binding and understand how it works with the AngularTS runtime.

What stays the same

An integration changes the language used to register AngularTS features. It does not introduce a second module system or component lifecycle. Modules, injection, components, directives, scopes, templates, and services behave the same way.

Choose by language

Use the binding for the language that produces your client-side code:

  • Closure JavaScript uses the extern file from the npm package.
  • ClojureScript, Java/J2CL, and Scala.js use Maven Central artifacts.
  • Dart uses pub.dev.
  • Gleam uses Hex.
  • Kotlin and the WebAssembly languages currently use source dependencies.

Every integration still needs @angular-wave/angular.ts on the page. Load it once and keep its version equal to the language binding version.

Start from a complete project

Open the guide for your language, install its required compiler, and begin with the linked todo project. The example shows the dependency, entry point, page, build output, and AngularTS startup together.

2 - Integration example projects

Find a complete todo project for each AngularTS language integration.

Use these projects when starting an application. Each one keeps the dependency, compiler configuration, AngularTS registration, page, and build output in one place.

IntegrationExample
Closure Compilerintegrations/closure/demo
ClojureScriptintegrations/closure/clojurescript/demo
Java/J2CLintegrations/closure/java/demo
Kotlin/JSintegrations/kotlin/examples/basic_app
Scala.jsintegrations/scala/examples/basic_app
Dartintegrations/dart/example/basic_app
Gleamintegrations/gleam/examples/basic_app
HarmonyOSintegrations/harmonyos/entry
AssemblyScriptintegrations/wasm/assemblyscript/examples/todo
Cintegrations/wasm/c/examples/todo
C++integrations/wasm/cpp/examples/todo
C#/.NETintegrations/wasm/csharp/examples/todo
Gointegrations/wasm/go/examples/basic_app
Rustintegrations/wasm/rust/examples/basic_app
Zigintegrations/wasm/zig/examples/todo

Copy the project structure that matches your language, then replace the todo model and markup one feature at a time.

3 - Closure Compiler

Compile AngularTS code with Closure ADVANCED optimization while preserving runtime API names through the packaged extern file.

Closure Compiler optimizes your JavaScript. AngularTS stays outside that output, and its extern file tells Closure which public names must not be renamed.

Install

Install @angular-wave/angular.ts and Closure Compiler in the application. Pass dist/externs/angular.js from the npm package to Closure, and compile the production output with ADVANCED optimization.

Load dist/angular-ts.umd.min.js before the compiled application file:

<script src="/node_modules/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script>
<script src="/dist/application.js"></script>

Production guidance

  • Treat the packaged extern as part of the AngularTS version; do not edit it.
  • Promote Closure warnings to errors.
  • Keep AngularTS external so the page loads only one runtime.
  • Use documented public APIs so Closure can preserve every accessed member.
  • Test the ADVANCED output, not only uncompiled source.

Complete example

Use integrations/closure/demo as a complete todo project, or browse all integration examples.

4 - ClojureScript

Create a Shadow CLJS application with the Maven facade, packaged externs, explicit injection, and the AngularTS browser runtime.

The angular-ts-cljs Maven artifact provides the angular-ts.core API and the Closure externs needed by an optimized ClojureScript build. Keep it on the same version as the AngularTS runtime loaded by the page.

Before you start

Install Java 21, Node.js, and Shadow CLJS. Create a project with src/main/todo/app.cljs, shadow-cljs.edn, and an HTML entry page.

Configure Shadow CLJS

Add the Maven artifact and tell Closure where the packaged extern file is:

{:source-paths ["src/main"]
 :dependencies [[io.github.angular-wave/angular-ts-cljs "0.37.0"]]
 :builds
 {:app {:target :browser
        :output-dir "public/js"
        :asset-path "/js"
        :modules {:app {:entries [todo.app]}}
        :compiler-options
        {:externs ["angular_ts/externs/angular.js"]
         :infer-externs true
         :warnings-as-errors true}}}}

Keep :warnings-as-errors true; extern or type-tag drift should stop the build instead of producing renamed runtime calls.

Register the application

Use angular-ts.core for fluent module registration. Dependency names stay explicit because optimized ClojureScript function names are not injectable names.

(ns todo.app
  (:require [angular-ts.core :as ng]
            [goog.object :as gobj]))

(defn create-model []
  (js-obj "newTodo" "" "tasks" #js []))

(defn create-controller [model]
  (gobj/set model "add" #(js/console.log "add"))
  model)

(defonce app
  (-> (ng/create-module "todo" [])
      (ng/model "todoModel" [] create-model)
      (ng/controller "TodoCtrl" ["todoModel"] create-controller)))

Use JavaScript objects and arrays at the framework boundary. Keep persistent Clojure collections inside domain code and convert them once when assigning a template-visible value.

Load and start the application

Load AngularTS before the compiled module and bootstrap after both are ready:

<main id="app" ng-controller="TodoCtrl as ctrl"></main>
<script src="angular-ts.umd.js"></script>
<script src="/js/app.js"></script>
<script>
  angular.bootstrap(document.getElementById('app'), ['todo']);
</script>

Build with npx shadow-cljs release app. Serve public over HTTP.

Production practices

  • Prefer the fluent facade; use the lower-level namespace only when no helper exists.
  • Keep *warn-on-infer* enabled and fix every inference warning.
  • Use explicit injection vectors for controllers, services, directives, and configuration blocks.
  • Load one AngularTS runtime even when several ClojureScript modules share the page.
  • Compile and test with Closure ADVANCED optimization before release.

Complete example

Use integrations/closure/clojurescript/demo as a complete Shadow CLJS todo project, or browse all integration examples.

5 - Java and J2CL

Create an AngularTS application in Java with the Maven bindings, annotation processor, J2CL compiler, and AngularTS runtime.

J2CL compiles Java to JavaScript. The angular-ts-java artifact supplies typed JsInterop bindings and an annotation processor; @angular-wave/angular.ts supplies the browser runtime. Keep both on the same version.

The Maven artifact includes the Java sources and AngularTS externs required by J2CL. Your application only generates and passes its own template extern.

Before you start

Install JDK 21, Maven, and Node.js. Create a Maven project with src/main/java for Java sources and a directory for the page that loads the compiled output.

Add the Java bindings

Add the version and regular dependency to pom.xml:

<properties>
  <angular.ts.version>0.37.0</angular.ts.version>
</properties>

<dependencies>
  <dependency>
    <groupId>io.github.angular-wave</groupId>
    <artifactId>angular-ts-java</artifactId>
    <version>${angular.ts.version}</version>
  </dependency>
</dependencies>

Use the same artifact as the annotation processor:

<annotationProcessorPaths>
  <path>
    <groupId>io.github.angular-wave</groupId>
    <artifactId>angular-ts-java</artifactId>
    <version>${angular.ts.version}</version>
  </path>
</annotationProcessorPaths>
<annotationProcessors>
  <annotationProcessor>
    org.angular.ts.processor.AngularClosureProcessor
  </annotationProcessor>
</annotationProcessors>

The dependency supplies its runtime extern automatically. The processor generates the application Closure entry point and template extern. Configure the J2CL Maven plugin with goog:angular.ts.generated.entrypoint and META-INF/externs/angular-ts-template.externs.js. The complete tested plugin configuration is in integrations/closure/java/demo/pom.xml.

Register the application

Mark the startup method with @AngularEntryPoint. Mark only Java types that templates access with @AngularTemplateApi.

@AngularEntryPoint
public static void start() {
  Angular.createModule("todo", new String[0])
      .controller("TodoCtrl", (TodoFactory) TodoController::new);
}

@JsFunction
interface TodoFactory {
  TodoController create();
}

The maintained todo demonstrates public fields, template methods, directives, and array replacement in integrations/closure/java/demo/src/main/java.

Load and start the application

Load AngularTS first, then the J2CL bundle, then bootstrap the registered module:

<main id="app" ng-controller="TodoCtrl as ctrl"></main>
<script src="angular-ts.umd.js"></script>
<script src="todo.js"></script>
<script>
  angular.bootstrap(document.getElementById('app'), ['todo']);
</script>

Build with mvn package. Serve the page over HTTP; do not open it through a file: URL.

Production practices

  • Use the published artifact as a dependency; never copy generated bindings into the application.
  • Keep Closure ADVANCED optimization enabled and keep template extern generation in the build.
  • Expose the smallest possible template API. Unannotated Java implementation details remain optimizable.
  • Replace Java arrays when their length changes so AngularTS observes the new collection identity.
  • Test the optimized J2CL output against the matching AngularTS bundle.

Complete example

Use integrations/closure/java/demo as a complete Maven and J2CL todo project, or browse all integration examples.

6 - Kotlin/JS

Add the Kotlin source binding, register an AngularTS module, and build the Kotlin/JS output.

The Kotlin integration provides typed access to AngularTS modules, injection, components, directives, services, and scopes.

Add the binding

The Kotlin binding is currently distributed as source. Add integrations/kotlin as a Gradle project dependency and use integrations/kotlin/examples/basic_app as the application structure. Enable a Kotlin/JS IR browser target with an executable binary.

Load the matching @angular-wave/angular.ts runtime before the Kotlin/JS output.

Guidance

  • Use typed injection tokens and explicit dependency order.
  • Prefer Kotlin wrappers over dynamic JavaScript access.
  • Keep JavaScript conversion at the edge of the application.
  • Build and test the optimized Kotlin/JS output used in deployment.

Complete example

Use integrations/kotlin/examples/basic_app as a complete Gradle project. integrations/kotlin/examples/web_components shows custom elements. See all integration examples.

7 - Android

Run server-rendered AngularTS screens in Android navigation and add native controls where the web page needs Android behavior.

Angular Native keeps the server and HTML in charge of most screens. Android owns navigation, system access, and any controls that need native behavior. You can move one control or one destination to native UI without turning the whole application into a client-rendered app.

Add the Android packages

Use the same version as @angular-wave/angular.ts:

dependencies {
    implementation("io.github.angular-wave:angular-native-core:

Version: 0.37.0

")
implementation("io.github.angular-wave:angular-native-navigation:

Version: 0.37.0

")
}

Add angular-native-browser only when external HTTP links should open in Android Custom Tabs:

dependencies {
    implementation("io.github.angular-wave:angular-native-browser:

Version: 0.37.0

")
}

The optional package discovers its route handler automatically. Without it, external links continue through Android’s normal system routing.

Add angular-native-paging only when a native collection reads from Android Paging 3:

dependencies {
    implementation("io.github.angular-wave:angular-native-paging:

Version: 0.37.0

")
}

Install optional platform SDKs only when a screen uses them:

dependencies {
    implementation("io.github.angular-wave:angular-native-credentials:

Version: 0.37.0

")
implementation("io.github.angular-wave:angular-native-maps:

Version: 0.37.0

")
implementation("io.github.angular-wave:angular-native-media:

Version: 0.37.0

")
}

Adding one of these packages is enough: Android registers it during application startup without an application-side registration call. credentials provides password and passkey operations through Android Credential Manager. media provides Media3 playback without adding Media3 to the base shell. maps adds the catalog map element; configure the Google Maps API key in the application manifest as required by the Maps SDK.

Do not install all optional packages by default. Keeping them separate avoids initializing SDKs and shipping dependencies that a screen does not use.

The app needs network access for server-rendered pages:

<uses-permission android:name="android.permission.INTERNET" />

System back and predictive-back gestures work through AndroidX Navigation. The navigation package opts into predictive back and makes the selected navigator host Android’s primary navigation fragment, including when bottom tabs switch. Do not add an Activity back callback for normal page history; add one only for temporary UI that must consume Back, such as an unsaved-form confirmation.

Native navigation uses one Android back stack. Use navigation.push, navigation.replace, navigation.pop, navigation.modal, navigation.deep-link, or navigation.external. Push, replace, modal, and deep-link accept default, none, slide, fade, cover, dive, or flip. Android disables explicit motion when system animations are off.

A navigation command returns phase: "accepted" with a transaction ID. Treat the matching navigation.change event as authoritative: it reports phase: "completed" after Android commits the destination, or phase: "cancelled" with reason: "interrupted", "destination-changed", "failed", or "closed". Android back gestures emit one completed change without replaying another navigation command.

AndroidX restores the route, route parameters, and back stack after process recreation. Angular Native also restores an unconsumed navigation action or modal result and delivers it once. It does not put response HTML or cached snapshot HTML into Android saved state: the restored destination requests that content again, avoiding Binder-size failures and stale page data.

Start with the complete demo. It shows the Application, AngularNativeActivity, path configuration, destinations, bottom tabs, file handling, and bridge components together.

Add a native control to HTML

ng-native-component replaces the marked rectangle when the page runs inside the Android shell. In a normal browser, the HTML fallback remains usable.

<input ng-model="profile.name" aria-label="Name" />
<div
  ng-native-component="text-field"
  ng-model="profile.name"
  data-props="{ label: 'Name' }"
></div>

The model works in both directions. AngularTS sends model, required, disabled, and validation state to Android. Android changes update the same ng-model and blur marks it as touched.

File selection uses the same model binding. Android returns content URI records, not filesystem paths:

<div
  ng-native-component="file-picker"
  ng-model="attachments"
  data-props="{ accept: ['image/*'], multiple: true }"
></div>

The native button opens Android’s document picker. Cancelling leaves the model unchanged. A successful selection assigns an array of { uri, name, size, type, persisted } records when multiple is true, or one record when it is false.

Native list and grid children require a unique key. The key lets Android reuse visible views and restore each item’s state while large collections scroll.

Native tabs use each child’s key as their controlled string value; native pager uses a zero-based integer value. Updating the model moves selection without recreating keyed child views. A swipe or tab press emits change, and scrollTo provides the same selection behavior for imperative native calls.

Native layout numbers use density-independent pixels. width and height set an exact size; without them, containers fill the available width and either wrap their content or fill the available height when the component is a scroll, scaffold, tabs, or pager. Use minWidth, minHeight, and spacing for constraints and gaps. horizontalAlignment accepts start, center, end, or stretch; verticalAlignment accepts top, center, bottom, or stretch. Layout updates keep keyed child views and reset omitted values to their documented defaults.

Style native controls with CSS

Native elements use the page’s computed CSS. Keep behavior and accessibility in HTML, then reuse classes instead of repeating presentation attributes. Angular Native resolves CSS variables, inheritance, and the cascade before it sends styles to Android.

<ng-native-text class="page-title" text="Pulse"></ng-native-text>
.page-title {
  color: var(--primary);
  font-family: Georgia, serif;
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-bold);
}

The supported CSS projection covers foreground and background colors, typography, dimensions and minimum sizes, padding, margins, gaps, borders, radii, opacity, shadows, text alignment, object-fit, and accent-color. CSS pixels map to Android density-independent pixels; font sizes map to scaled pixels. Class and root-theme changes update mounted native views without recreating keyed children.

Use the semantic tokens from Angular.css when the web and native versions of a screen share a theme. Applications can override --background, --foreground, --primary, --accent, --muted, spacing, radius, and typography tokens in their own stylesheet. Android receives resolved values, so it does not need to parse CSS or know which token library supplied them.

Use ng-native-event for native events and ng-native for methods or platform capabilities. Calls are ignored when no native bridge is available, so keep a normal HTML path for essential actions.

For current location, declare coarse or fine location permission in the Android manifest, request it while the destination is visible, then call the location capability:

<button
  ng-native="geolocation.current"
  data-on-result="position = $result"
  data-on-error="locationError = $error.message"
>
  Use my location
</button>
<p ng-if="position">{{ position.latitude }}, {{ position.longitude }}</p>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Call permissions.request first when geolocation.status reports that access has not been granted. Its accuracy result is fine, coarse, or null, so a coarse-only grant is reported as usable without claiming precise access. geolocation.current rejects with denied, unavailable, or interrupted instead of returning stale coordinates. Aborting the browser call cancels the Android location request.

Use the system camera when a page needs a new image. Angular Native stores the capture behind a content URI and deletes an unfinished capture:

<button
  ng-native="camera.capture"
  data-on-result="photo = $result"
  data-on-error="cameraError = $error.message"
>
  Take photo
</button>
<img ng-if="photo" ng-src="{{ photo.uri }}" alt="Captured photo" />

The result is { uri, name, size, type }. Check camera.status before showing the action on devices without a camera.

Optional native capabilities appear in the environment capability list only when their artifact is installed. Use $native.supports(target, method) before showing an action. Media sources must use https, content, or android.resource; local file URLs are rejected. Credential and media work is cancelled when its destination is removed. The map element preserves its camera position, forwards Android lifecycle changes, and emits ready, cameraChange, markerClick, and mapClick events.

Call connectivity.watch or lifecycle.watch to receive change events for the current destination. Call the matching unwatch method when the page no longer needs them. Removing the destination also stops both subscriptions.

Call window.status for the current window width, height, orientation, safe-area insets, and width and height size classes. Use window.watch when a layout must react while the app is resized, rotated, folded, or moved into split screen. Its change event also includes displayFeatures; folds and hinges report their orientation, state, separating behavior, and bounds in density-independent pixels. Call window.unwatch when the destination no longer needs updates.

Application libraries can add a native service by implementing NativeCapabilityProvider. The provider receives a destination-scoped NativeCapabilityContext; use its emit method instead of retaining the bridge or WebView. Targets, methods, and event names must use lowercase kebab-case. Close listeners and other resources from NativeCapability.close.

See the generated native element catalog for every element name, property, method, and event. The generated native capability catalog lists each service, artifact, availability rule, permission, method, and event.

Add an application component

Implement NativeElementProvider in the Android library that owns the UI. A provider returns NativeElementDefinition values backed by a normal Android View factory or composeNativeElement. Add the metadata compiler to that library:

plugins {
    id("com.android.legacy-kapt")
}

dependencies {
    implementation("io.github.angular-wave:angular-native-navigation:

Version: 0.37.0

")
kapt("io.github.angular-wave:angular-native-elements-compiler:

Version: 0.37.0

")
}

Use the same com.android.legacy-kapt version as the application’s Android Gradle plugin. List every provided name in @RegisterNativeElementProvider("task-card", "task-status"). The compiler generates Java service-provider metadata, and Angular Native discovers the provider when the shell starts. The application does not edit or call a central component registry.

The published io.github.angular-wave:angular-native-custom-elements-sample artifact contains the complete task-card View provider and task-status Compose provider. Use it as a working reference; keep application components in the Android library that owns their UI and dependencies.

Compose factories receive current properties and a scope with context, lifecycleOwner, savedStateOwner, and emit:

factory = composeNativeElement { properties ->
    Card(modifier = Modifier.clickable { emit("click") }) {
        Text(properties.string("text"))
    }
}

Keep component names in the library that implements them. Reject unknown input in the definition, emit plain JSON event data, release listeners in dispose, and save only transient UI state in saveState.

Security

Every destination has a random session token. Android accepts bridge calls only from the destination’s original HTTP or HTTPS origin and rejects stale sessions. Intent URLs are limited to http, https, mailto, tel, and geo. File results use content URIs instead of filesystem paths.

Permission and file requests run only while their destination is visible. Calls can be aborted with an AbortSignal; Android then suppresses queued work and late replies. Destination removal rejects unfinished calls as interrupted.

Validate an Android change

make -C integrations/android release-check
make -C integrations/kotlin check
make generated-check

release-check recreates an isolated Maven repository, publishes every Android artifact into it, and compiles and shrinks a clean consumer. AngularTS coordinates resolve only from that repository, so a stale artifact in mavenLocal() or Maven Central cannot hide an incomplete publication. It also signs all nine publications with a disposable test key and verifies every signature without requiring release credentials.

The root release workflow publishes every Android artifact to Maven Central, waits for its public POM, and blocks the npm/GitHub release if one is missing.

Run the Pulse reference application

Pulse demonstrates a native photo feed, profiles, activity updates, native file selection, and multipart uploads without application-specific Kotlin or Java code. Start one Android emulator, then run:

make -C integrations/android sample-social-run

The command starts its local Node server, installs the sample, and opens it in the emulator.

Pulse mounts one native scaffold containing its app bar, changing content, and bottom navigation. Its path configuration sets toolbar_enabled to false because the scaffold owns the app bar. Leave this property unset when the fragment toolbar should remain visible. The sample demonstrates fade, slide, cover, dive, and flip transitions on different routes.

The generic AngularNativeHostActivity reads its start URL and path configuration from Android manifest metadata.

Native collections

Use list for one-dimensional data and grid for columns. Every child must have a stable key, an element name, and optional props. Updating the collection runs a keyed diff. Unchanged visible children keep their Android view, state, event wiring, and accessibility state.

Set swipeEnabled to emit swipe with the item key, index, and logical start or end direction. Set reorderEnabled to enable long-press drag and emit move with the key and old and new indexes. Treat both as controlled events: update the server or client model and send the resulting child order back to the collection. list-item.selected is controlled in the same way.

pull-to-refresh emits refresh and keeps showing its progress indicator while refreshing is true. Collection scroll position and mounted child state round-trip through the normal native element save and restore lifecycle.

For datasets backed by Android Paging 3, use NativePagingAdapter from angular-native-paging. Its loadState event reports refresh, prepend, append, empty, retryable, and end-of-list state. Attach NativePagingLoadStateAdapter as a footer when the screen needs localized loading, retry, and end labels. Call the Paging adapter’s standard retry() and refresh() methods from those actions.

When a physical device is available, run the additional hardware checks:

make -C integrations/android physical-check

You can also dispatch the Android Physical Device GitHub Actions workflow for an exact commit. It records compatibility and performance evidence but does not block a release. Portable CI, emulator-connected tests, package validation, and clean Maven consumption remain mandatory.

The command rejects emulators, runs every connected test, then performs a keyed update and scroll over 10,000 items. It captures AndroidX frame and memory metrics and rejects updates that exceed the allocation cap. It also measures 1,000 JavaScript-to-Kotlin bridge calls and enforces bridge-latency and WebView host-process memory ceilings. Versioned limits in config/benchmark-budgets.json also fail the physical gate when startup, frame, or memory metrics regress. The same device run generates a startup baseline profile. Core and navigation AARs also ship narrow seed rules, so a clean consumer receives useful startup guidance before the measured profile is generated for the final application.

Dialogs and other overlays

Use dialog, bottom-sheet, drawer, menu, snackbar, and tooltip for transient native UI. Each supports show, hide, and dismiss. Their events report user actions such as confirm, cancel, selection, and snackbar actions. Only one overlay can own a destination at a time; showing another dismisses the current one and returns accessibility focus to its trigger.

Drawer, dialog, and bottom-sheet restore open after destination recreation. Menu, snackbar, and tooltip restore closed because they represent a temporary interaction rather than destination state. Disposing the destination dismisses its overlay without retaining its Activity, Fragment, WebView, or scope. See the native element catalog for each element’s properties and event payloads.

8 - HarmonyOS

Run AngularTS pages in ArkWeb and place ArkUI controls over the page when a screen needs native HarmonyOS behavior.

Angular Native keeps HTML and the server in charge of the application. The HarmonyOS shell owns navigation, system access, and controls that need ArkUI. The same page continues to work in a normal browser because native elements keep their HTML fallback.

All shared native elements and capabilities have HarmonyOS providers and portable tests. The packages are not production releases until the official API 26 build, device evidence, and public OHPM consumer checks pass.

Choose packages

Every application starts with @angular-wave/angular-native-harmony-core. Add only the packages used by the application:

PackageAdd it when
@angular-wave/angular-native-harmony-navigationAngularTS routes should use ArkUI navigation and system back.
@angular-wave/angular-native-harmony-browserExternal HTTP links should use the HarmonyOS resolver.
@angular-wave/angular-native-harmony-credentialsA page uses passkeys.
@angular-wave/angular-native-harmony-mapsA page contains the native map element.
@angular-wave/angular-native-harmony-mediaA page controls native audio or video playback.
@angular-wave/angular-native-harmony-pagingA large keyed collection needs incremental native updates.
@angular-wave/angular-native-harmony-compilerA library defines additional native elements for its applications.

Do not add every optional package. Separate packages keep platform SDKs and initialization work out of applications that do not use them.

Prepare the project

Install Huawei’s official command-line tools and the HarmonyOS API 26 SDK. The tool bundle must provide ohpm, hvigorw, and codelinter. Point AngularTS at an existing installation:

export HARMONY_COMMAND_LINE_TOOLS_HOME="$HOME/command-line-tools"
make -C integrations/harmonyos toolchain-check

The bootstrap target can install a local official archive without changing the user’s shell. It deliberately requires a SHA-256 digest; do not run an unverified SDK archive.

HARMONY_COMMAND_LINE_TOOLS_ARCHIVE="$HOME/Downloads/command-line-tools-linux.zip" \
HARMONY_COMMAND_LINE_TOOLS_SHA256="<verified digest>" \
make -C integrations/harmonyos bootstrap

Open integrations/harmonyos in DevEco Studio when signing or device selection needs the IDE. The entry module is the packaged proof application. It loads the same AngularTS distribution that a release ships, not a development server.

Add native UI to a page

Use AngularTS native elements in HTML. AngularTS resolves CSS, accessibility data, model bindings, and events, then sends normalized values to the HarmonyOS shell. ArkUI renders the native control on the same-layer surface. Application code does not register built-in controls and does not contain ArkTS component mappings.

Start with the packaged proof.html. The available elements and properties are listed in the native element catalog.

Keep reusable colors, spacing, type sizes, and control defaults in CSS. Do not repeat native properties on every element. The browser computes the styles; HarmonyOS receives density-independent values and applies system font scale, safe areas, right-to-left layout, dark mode, and reduced motion.

Use Map Kit

Maps are optional because Huawei Map Kit is not needed by most applications. Add the maps package, then pass its same-layer controller factory to the shell. The page still uses the shared native map element; it does not call ArkTS or Map Kit directly.

import { harmonyMapControllerFactory } from '@angular-wave/angular-native-harmony-maps';

new AngularNativeDestination({
  ...options,
  nativeControllerFactory: harmonyMapControllerFactory,
});

The factory renders MapComponent, keeps markers and camera state in sync, forwards map events, and handles the native map methods declared by the shared contract. The kitchen sink uses this exact provider wiring.

Map Kit requires Huawei’s HarmonyOS API 26 SDK. The public OpenHarmony API 23 lane type-checks the provider against a declaration-only compatibility stub, but deliberately excludes the maps HAR and the kitchen-sink HAP from its build. Only the official API 26 release gate can prove that Map Kit package.

Run the sample apps

Use the kitchen sink to inspect every native element and its current contract. The page is generated from the shared catalogs, so checks fail when a property, event, method, or capability is missing.

make -C integrations/harmonyos/samples/kitchen-sink compile

Pulse is the larger sample. Android and HarmonyOS use the same HTML, JavaScript, routes, server, and assets. The HarmonyOS project contains only the Stage-model shell.

Start its server first:

node integrations/native/samples/pulse/server.mjs --port 4175

Then build the HarmonyOS shell:

make -C integrations/harmonyos/samples/pulse compile

127.0.0.1 works only when the device forwards that port. On a physical or cloud device, pass a reachable HTTPS or LAN URL as the pulseUrl Want parameter. The shell rejects credentials, fragments, invalid URLs, and non-HTTP schemes.

Load server-rendered pages

Use HTTPS for pages loaded from a server. A phone’s localhost is the phone, not the development machine. Use a reachable development hostname or an hdc port reverse while testing locally. Do not compile a workstation IP address into the application.

The AngularTS router remains the source of route intent. HarmonyOS mirrors committed routes into NavPathStack; ArkUI must not invent a second application history. External HTTP and HTTPS locations go through the system resolver.

Security

Every destination receives a random session token. The bridge accepts calls only from that destination’s normalized origin and rejects duplicate request identifiers, undeclared methods, malformed native trees, oversized messages, and calls made after disposal. Capability errors sent to a page use stable public codes rather than platform exception details.

Declare only permissions used by installed capabilities. Keep credentials and private keys out of pages, bridge payloads, package metadata, and repository files. See the HarmonyOS security model before adding a provider.

Validate a change

Portable checks do not require the HarmonyOS SDK:

make -C integrations/harmonyos check

Relevant pull requests and updates to master also run a pinned public OpenHarmony API 23 compiler audit and package build. This catches ArkTS, public API, and packaging regressions without treating that compatibility lane as an API 26 release. It retains the exact commit-scoped packages for 14 days. The API 23 build produces the proof and Pulse HAPs plus six compatible HARs; credentials and maps remain API 26-only build gates because their Huawei kits are not available in the public OpenHarmony SDK.

The release gate uses the official ArkTS compiler and Code Linter, builds every HAR and HAP, and compiles an isolated application against the built HAR files:

make -C integrations/harmonyos release-check

Before publication, publication-check also requires complete Android/Harmony contract parity and successful device or Huawei cloud evidence for the exact commit. A green portable check is not a substitute for that evidence.

The retained release bundle contains the eight HARs, license, exact dependency manifest, SPDX 2.3 SBOM, SLSA provenance statement, package manifest, and SHA256SUMS. Evidence capture copies that exact directory. Publication rejects missing metadata, stale package identity, altered HARs, altered metadata, and unchecksummed files.

The native workflow accepts schema-v2 evidence only when the device run reports no failed or skipped tests, names every contract from the current shared catalogs exactly once, passes accessibility and performance checks, and supplies JUnit output plus phone and tablet screenshots. This prevents stale counts, an empty cloud job, or a single smoke test from being treated as release evidence.

Publish and verify

OHPM requires an authenticated account, an encrypted private key, and an interactive terminal. After the publication gate passes, submit all packages in dependency order:

make -C integrations/harmonyos publish-release

OHPM reviews submissions before making them public. After approval, compile a fresh application using exact versions from the registry:

make -C integrations/harmonyos verify-published

9 - Scala.js

Create an sbt Scala.js application with typed AngularTS models, controllers, a linked browser bundle, and the matching runtime.

The Scala artifact provides typed facades and idiomatic builders. Scala.js links the application; @angular-wave/angular.ts remains the browser runtime. Keep the Maven and npm versions equal.

Before you start

Install JDK 21, sbt, and Node.js. Start with project/plugins.sbt, build.sbt, src/main/scala, and an HTML entry page.

Enable Scala.js

Add the Scala.js plugin:

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0")

Enable the plugin and add the facade with %%%. sbt expands the standard Scala.js and Scala binary suffixes automatically.

scalaVersion := "3.3.3"
enablePlugins(ScalaJSPlugin)

libraryDependencies +=
  "io.github.angular-wave" %%% "angular-ts-scala" % "0.37.0"

scalaJSUseMainModuleInitializer := true

Register and bootstrap the application

Model tokens can be registered directly as controllers; no identity factory is needed.

package todo

import angular.ts.*
import org.scalajs.dom.document
import scala.scalajs.js

final class TodoModel(var newTodo: String = "") extends js.Object

object App:
  @main def main(): Unit =
    val app = AngularTS.createModule("todo")
    val model = AngularTS.token[Model[TodoModel]]("todoModel")

    app
      .model(model, () => new TodoModel())
      .controller("TodoCtrl", model)

    AngularTS.bootstrap(document.body, Seq(app.name))

Use classes extending js.Object for values that cross into AngularTS. Keep ordinary Scala domain types behind that boundary.

Load the browser runtime

Load AngularTS before the linked Scala.js output:

<main ng-controller="TodoCtrl as ctrl">
  <input ng-model="ctrl.newTodo" />
</main>
<script src="angular-ts.umd.js"></script>
<script src="main.js"></script>

Run sbt fastLinkJS while developing and sbt fullLinkJS for production. Point the page at the generated main.js and serve it over HTTP.

Production practices

  • Prefer typed tokens, builders, and injection helpers over js.Dynamic.
  • Keep AngularTS models as the shared reactive boundary rather than mirroring state into DOM-root scopes.
  • Replace Scala collections with js.Array only where the browser-facing model requires JavaScript collection semantics.
  • Use fullLinkJS and test the linked output against the release runtime.
  • Keep the Scala.js artifact and AngularTS runtime on the same version.

Complete example

Use integrations/scala/examples/basic_app as a complete sbt todo project, or browse all integration examples.

10 - Dart

Install angular_ts, create a typed Dart component, and load it with the matching AngularTS runtime.

The angular_ts package gives Dart code typed access to AngularTS modules, injection, components, directives, services, and scopes.

Install

Add the facade and Web API package:

dart pub add angular_ts web

Compile the entry point:

dart compile js web/main.dart -o web/main.dart.js

Load @angular-wave/angular.ts before main.dart.js, keep both packages on the same version, and serve the page over HTTP. The package page contains a complete first component.

Guidance

  • Use typed tokens and inject0 through inject8 for dependencies.
  • Expose template-facing controllers with @JSExport() and createJSInteropWrapper().
  • Keep JavaScript conversion at the edge of the application.
  • Test the compiled JavaScript in a real page.

Complete example

Use integrations/dart/example/basic_app as a complete todo project, or browse all integration examples.

11 - Gleam

Install angular_ts, register an AngularTS module from Gleam, and compile the application to JavaScript.

The angular_ts package supplies typed Gleam functions for modules, dependency injection, components, and application startup.

Install

gleam add angular_ts

Build with gleam build --target javascript. Load the matching @angular-wave/angular.ts runtime before the generated application script and serve the page over HTTP.

Guidance

  • Keep foreign JavaScript functions at the edge of the application.
  • Prefer typed package functions over untyped JavaScript values.
  • Keep the Gleam package and AngularTS runtime on the same version.
  • Test the generated JavaScript in a real page.

Complete example

Use integrations/gleam/examples/basic_app for a component, injected store, and custom element, or browse all integration examples.

12 - AssemblyScript WebAssembly

Connect AssemblyScript logic to an AngularTS scope through the shared Wasm ABI.

AngularTS owns the page while AssemblyScript handles domain logic in a Wasm module.

Add the binding

The binding is distributed as source. Import integrations/wasm/assemblyscript/src/angular_ts.ts, compile the guest with AssemblyScript’s incremental runtime and exported runtime helpers, then load the result through app.wasm(...).

The host adapter binds the guest to a scope and releases it when that scope is destroyed.

Guidance

  • Use typed fields for values exchanged with AngularTS.
  • Batch related writes into one update.
  • Copy transient data across the ABI instead of retaining host buffers.
  • Keep durable shared state in AngularTS models.

Complete example

Use integrations/wasm/assemblyscript/examples/todo, or browse all integration examples.

13 - C WebAssembly

Connect C domain logic to an AngularTS scope with explicit memory ownership.

AngularTS owns the page while C handles domain logic in a Wasm module.

Add the binding

The binding is distributed as source. Add include/angular_ts_wasm.h and src/angular_ts_wasm.c to the guest, include the typed field contract used by the feature, and compile for wasm32-freestanding without an entry point.

Load the result through app.wasm(...) and bind it to the component scope from the host adapter.

Guidance

  • Release every owned result and cancel watches before freeing callback state.
  • Never retain temporary host buffers.
  • Apply related field changes through one update.
  • Keep durable shared state in AngularTS models.

Complete example

Use integrations/wasm/c/examples/todo, or browse all integration examples.

14 - C++ WebAssembly

Connect C++ domain logic to an AngularTS scope with typed access and RAII cleanup.

AngularTS owns the page while C++ handles domain logic in a Wasm module.

Add the binding

The binding is distributed as source and is header-only. Add integrations/wasm/cpp/include to the compiler search path, include angular_ts/wasm.hpp, and compile for wasm32-wasi without an entry point.

Load the result through app.wasm(...) and bind it to the component scope from the host adapter.

Guidance

  • Use the C++ scope, watch, and owned result wrappers instead of raw handles.
  • Keep browser objects out of guest state.
  • Group related field changes in one transaction.
  • Keep durable shared state in AngularTS models.

Complete example

Use integrations/wasm/cpp/examples/todo, or browse all integration examples.

15 - C# and .NET WebAssembly

Connect C# domain logic to an AngularTS scope with .NET WebAssembly.

AngularTS owns the page while C# handles domain logic in a .NET Wasm module.

Add the binding

The binding is distributed as source. Install .NET 8 and the wasm-tools workload, add a project reference to integrations/wasm/csharp/src/AngularTs.Wasm.csproj, and publish the application for browser-wasm.

dotnet workload install wasm-tools
dotnet publish -c Release -r browser-wasm

The host adapter initializes .NET with the AngularTS imports and binds the guest to a scope.

Guidance

  • Dispose watches and decoded values deterministically.
  • Keep JavaScript interop in the host adapter.
  • Exchange plain snapshots for durable AngularTS model state.
  • Pin the .NET SDK and workload used to build the application.

Complete example

Use integrations/wasm/csharp/examples/todo, or browse all integration examples.

16 - Go WebAssembly

Connect Go domain logic to an AngularTS scope with a js/wasm guest.

AngularTS owns the page while Go handles domain logic in a Wasm module.

Add the binding

The binding is distributed as source. Point go.mod at integrations/wasm/go, generate the application’s AngularTS metadata, compile for js/wasm, and place Go’s wasm_exec.js beside the output.

go generate ./...
GOOS=js GOARCH=wasm go build -o main.wasm .
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

Load the generated AngularTS bootstrap module from the page.

Guidance

  • Keep callback wrappers reachable while AngularTS owns them.
  • Release watches when their scope is destroyed.
  • Batch related scope updates.
  • Keep durable shared state in AngularTS models.

Complete example

Use integrations/wasm/go/examples/basic_app, or browse all integration examples.

17 - Rust WebAssembly

Connect Rust domain logic and services to AngularTS with wasm-bindgen.

AngularTS owns the page while Rust handles domain logic and services in a Wasm module.

Add the binding

The binding is distributed as source. Add integrations/wasm/rust/crates/angular-ts as a path dependency, install the wasm32-unknown-unknown target and wasm-bindgen-cli, then compile the guest and generate its Web loader.

cargo build --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/app.wasm \
  --target web \
  --out-dir pkg

Initialize the generated module before bootstrapping AngularTS.

Guidance

  • Use typed fields instead of raw paths.
  • Propagate contract and decode failures.
  • Keep watch guards for exactly as long as the subscription is needed.
  • Keep durable shared state in AngularTS models.

Complete examples

Use integrations/wasm/rust/examples/basic_app for application registration and integrations/wasm/rust/examples/scope_bridge for direct scope exchange. See all integration examples.

18 - Zig WebAssembly

Connect Zig domain logic to an AngularTS scope with typed fields and explicit ownership.

AngularTS owns the page while Zig handles domain logic in a Wasm module.

Add the binding

The binding is distributed as source. Add integrations/wasm/zig/src/angular_ts.zig as an angular-ts module and compile the guest for wasm32-freestanding without an entry point.

Load the result through app.wasm(...) and bind it to the component scope from the host adapter.

Guidance

  • Call deinit() for owned decoded results and watch registrations.
  • Use allocator-aware decoding only when the application needs custom ownership.
  • Apply related field changes through one update.
  • Keep durable shared state in AngularTS models.

Complete example

Use integrations/wasm/zig/examples/todo, or browse all integration examples.