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.
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.
| Integration | Example |
|---|
| Closure Compiler | integrations/closure/demo |
| ClojureScript | integrations/closure/clojurescript/demo |
| Java/J2CL | integrations/closure/java/demo |
| Kotlin/JS | integrations/kotlin/examples/basic_app |
| Scala.js | integrations/scala/examples/basic_app |
| Dart | integrations/dart/example/basic_app |
| Gleam | integrations/gleam/examples/basic_app |
| HarmonyOS | integrations/harmonyos/entry |
| AssemblyScript | integrations/wasm/assemblyscript/examples/todo |
| C | integrations/wasm/c/examples/todo |
| C++ | integrations/wasm/cpp/examples/todo |
| C#/.NET | integrations/wasm/csharp/examples/todo |
| Go | integrations/wasm/go/examples/basic_app |
| Rust | integrations/wasm/rust/examples/basic_app |
| Zig | integrations/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.
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:
| Package | Add it when |
|---|
@angular-wave/angular-native-harmony-navigation | AngularTS routes should use ArkUI navigation and system back. |
@angular-wave/angular-native-harmony-browser | External HTTP links should use the HarmonyOS resolver. |
@angular-wave/angular-native-harmony-credentials | A page uses passkeys. |
@angular-wave/angular-native-harmony-maps | A page contains the native map element. |
@angular-wave/angular-native-harmony-media | A page controls native audio or video playback. |
@angular-wave/angular-native-harmony-paging | A large keyed collection needs incremental native updates. |
@angular-wave/angular-native-harmony-compiler | A 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
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.