This project demonstrates Angular 22 running on React Native's Fabric renderer via Angular Native (@ng-native), paired with @nativescript/react-native (9.1.0) to instantiate and control native iOS (SceneKit, Metal, UIKit) and native Android (android.graphics.Canvas, android.view.View, android.os.*, java.lang.*) APIs directly from TypeScript—co-located with Angular signals.
ns-ng-native-small.mov
- Real-Time 3D
SceneKitMetal Viewport (SCNView)- Dynamically loads Apple's
/System/Library/Frameworks/SceneKit.frameworkat runtime from TypeScript. - Allocates an
SCNViewon the iOS UI thread with4× MSAA, physically based rendering (SCNLightingModelPhysicallyBased), HDR camera bloom (SCNCamera.bloomIntensity), a 3-point studio lighting rig (SCNLight), and a multi-axis orbital gimbal sculpture (SCNTorus,SCNBox,SCNSphere) animated at 60–120 Hz viaSCNAction.
- Dynamically loads Apple's
- Embedded Native
UIKitControls (UIVisualEffectView+UISegmentedControl)- Creates a real iOS
UIVisualEffectView(UIBlurEffectStyleSystemChromeMaterialDark) containing aUISegmentedControl. - Subclasses
NSObjectat runtime in TypeScript (NSObject.extend) to register an Objective-C@selector(onSegmentChanged:)target-action that mutates the liveSCNScenegeometry directly on the UI thread when tapped.
- Creates a real iOS
- Live iOS Runtime & Hardware Introspection
- Queries
MTLCreateSystemDefaultDevice(),NSProcessInfo.processInfo,UIDevice.currentDevice,UIScreen.mainScreen, and__nativeScriptNativeApi.getMetadataStats()directly over JSI, and includes a live 5,000-call Objective-C JSI micro-benchmark (~200–500 nanoseconds per call on the UI thread).
- Queries
- Real-Time 3D
android.graphics.CanvasOrbital Viewport (src/nativescript/android-studio.ts)- Uses
@nativescript/react-native's Android JSI runtime (globalThis.android.*,globalThis.java.*) to allocate nativeandroid.graphics.Bitmap(ARGB_8888),android.graphics.Canvas,android.graphics.Paint,android.graphics.Path, andandroid.graphics.drawable.BitmapDrawablebuffers directly from TypeScript. - Renders the matching 3-axis orbital gimbal rings, 3D geometry primitives (
Torus Ring,Chamfer Box,Geodesic), specular highlights, and 6 harmonic satellites at 60 fps with interactive touch-drag 3D orbit.
- Uses
- Native Android Segmented Bar
- Renders and attaches a custom
BitmapDrawable+ native touch handler (MotionEvent+HapticFeedbackConstants.CONTEXT_CLICK) onto the FabricReactViewGroupfor instant geometry switching.
- Renders and attaches a custom
- Live Android Runtime & Hardware Introspection
- Queries
android.os.Build,java.lang.Runtime.getRuntime(),android.app.ActivityManager.MemoryInfo,android.os.PowerManager,android.util.DisplayMetrics, andandroid.os.SystemClockdirectly from TypeScript over JSI/JNI, plus a liveandroid.os.Process.myPid()JSI/JNI micro-benchmark.
- Queries
Normally, a React Native or Angular Native app can only access native platform APIs that have been explicitly wrapped and compiled into C++ JSI HostObjects, TurboModules, or Fabric ViewManagers ahead of time.
@nativescript/react-native embeds the NativeScript JSI Runtime directly into the Hermes JavaScript engine on both platforms:
- Precompiled iOS SDK Metadata +
libffiDynamic Dispatch- During the CocoaPods build (
NativeScriptReactNative.podspec), a metadata generator inspects the active Xcode iOS SDK headers and compiles a compact binary metadata table (metadata.arm64.bin/metadata.x86_64.bin) describing every Objective-C class, protocol, selector, C function, struct, and enum in the iOS SDK (~34,000+ symbols). - At runtime, when TypeScript accesses
globalThis.SCNViewor callsMTLCreateSystemDefaultDevice(), NativeScript's JSI host object looks up the symbol in the metadata table and invokesobjc_msgSendor the C function pointer directly vialibffi—synchronously and in-process.
- During the CocoaPods build (
- Dual-Runtime Installation (JS Thread + UI Worklet Thread)
- Calling
NativeScript.installWorklets(require('react-native-worklets'))andNativeScript.init()installs the Objective-C JSI bindings into both the main Hermes JS runtime and thereact-native-workletsUI runtime so"worklet"functions passed toNativeScript.runOnUI(...)execute synchronously ondispatch_get_main_queue().
- Calling
- Fabric
UIKitViewHost (<ns-uikit-view>)@nativescript/react-nativeships a Fabric component (NativeScriptUIViewon iOS) that accepts anativeViewHandlestring (0x...pointer address) created byNativeScript.registerNativeView(uiView)on the UI thread and mounts theUIView *directly inside the Fabric layout tree.
- Gradle Classpath Metadata Generation (
nativescript.gradle)- Applying
../../node_modules/@nativescript/react-native/android/nativescript.gradleinandroid/app/build.gradleregisters:app:generateNativeScriptMetadataDebug, which scansandroid.jarand every resolved AAR/class directory on the app's classpath (~120,000+ Java/Kotlin methods and ~46,000+ fields) intotreeNodeStream.dat,treeStringsStream.dat, andtreeValueStream.dat.
- Applying
- In-Process JSI ↔ JNI Attachment (
com.tns.Runtime.attachToHostRuntime)- At runtime, calling
NativeScriptNativeApi.install("")extracts the metadata assets and attacheslibnativescript_rn.sodirectly to React Native'sjsi::Runtime *pointer (reactContext.getJavaScriptContextHolder().get()), exposingglobalThis.java,globalThis.android,globalThis.androidx, and the app's own packages (globalThis.org.nativescript.ngnativewithns.*) synchronously in TypeScript.
- At runtime, calling
1. Registering the Cross-Platform Host View in Angular Native (src/nativescript/ns-uikit-view.ts)
Angular Native (@ng-native/core) maps component selectors to React Native Fabric component names through registerElement. On iOS it maps to NativeScriptUIView; on Android it maps to RCTView with collapsable={false} so the underlying ReactViewGroup is preserved for nativeID lookup and BitmapDrawable attachment:
import { ChangeDetectionStrategy, Component, NO_ERRORS_SCHEMA, input } from '@angular/core';
import { registerElement } from '@ng-native/core';
registerElement('ns-uikit-raw', () => (detectIsAndroid() ? 'RCTView' : 'NativeScriptUIView'));2. Building Native iOS SCNView and UIKit Views on the UI Thread (src/nativescript/scene-worklets.ts)
Inside "worklet" functions on iOS, any Objective-C class in the iOS SDK can be instantiated and mutated on the UI thread and registered with Fabric via __ns_registerNativeView(view):
export const SceneWorklets = {
buildSceneKitStudioWorklet(payload: SceneUpdatePayload) {
'worklet';
const g = globalThis as Record<string, any>;
// Dynamically load SceneKit.framework at runtime
g.NSBundle.bundleWithPath('/System/Library/Frameworks/SceneKit.framework')?.load();
// Instantiate a real iOS SCNView and SCNScene
const scnView = g.SCNView.alloc().initWithFrame({
origin: { x: 0, y: 0 },
size: { width: 380, height: 290 },
});
const scene = g.SCNScene.scene();
scnView.scene = scene;
scnView.allowsCameraControl = true;
scnView.antialiasingMode = 2; // SCNMultisampling4X
// Retain the view on globalThis and return its native pointer handle ('0x...') to bind to <ns-uikit-view>
g.__ngNativeSceneState = { scnView, scene };
const scnViewHandle = g.interop?.handleof?.(scnView)?.toHexString?.() ?? '';
return { scnViewHandle };
},
};Note on
@ng-native/metroHMR + Worklets:@ng-native/metrowraps top-level function declarations in.tsfiles with an HMR proxy (cell.stays), which would hide the__workletHashproperty attached byreact-native-worklets/plugin. Exporting worklets as methods on an object (export const SceneWorklets = { ... }) preserves__workletHashuntouched while keeping full TypeScript type checking.
3. Rendering 3D android.graphics.Canvas Views via JSI/JNI (src/nativescript/android-studio.ts)
On Android, src/nativescript/android-studio.ts installs NativeScriptNativeApi on the Hermes runtime and drives native android.graphics.Bitmap, Canvas, Paint, Path, and BitmapDrawable objects directly from TypeScript:
const Bitmap = g.android.graphics.Bitmap;
const Canvas = g.android.graphics.Canvas;
const Paint = g.android.graphics.Paint;
const BitmapDrawable = g.android.graphics.drawable.BitmapDrawable;
state.viewportBitmap = Bitmap.createBitmap(680, 480, Bitmap.Config.ARGB_8888);
state.viewportCanvas = new Canvas(state.viewportBitmap);
state.viewportDrawable = new BitmapDrawable(resources, state.viewportBitmap);4. Connecting Angular Signals to Native Runtime APIs (src/nativescript/nativescript-lab.service.ts)
NativeScriptLabService detects the active platform (ios vs android), initializes the corresponding NativeScript JSI bridge, and dispatches live scene updates whenever an Angular signal (geometry, theme, speed, roughness, wireframe) changes.
When building an Angular Native (or React Native) app that needs specialized native capabilities—such as a 3D SceneKit viewport, custom android.graphics.Canvas hardware pipelines, AVFoundation audio graphs, or platform controls—you traditionally have two choices:
| Approach | What It Requires | Friction & Limitations |
|---|---|---|
| 1. Third-Party Wrapper Library | Hope someone built and maintained a Fabric-compatible wrapper for your exact iOS and Android APIs, then write an Angular Native directive around it. | • Often unmaintained or incompatible with the New Architecture (Fabric). • Only exposes the tiny subset of props the library author decided to wrap. • Every library adds native pod/Gradle dependencies and increases binary maintenance. |
| 2. Custom TurboModule + Fabric ViewManager | Write Objective-C++ / Swift and Kotlin / C++ glue code, Codegen specs, ShadowNodes, and ViewManager classes in ios/ and android/. |
• Every time you want to tweak a SCNCamera bloom property, add a SCNLight, or read a new Build / ActivityManager metric, you must edit native code and rebuild Xcode / Gradle.• Splits application logic across TypeScript, Objective-C++, Swift, and Kotlin. |
3. @nativescript/react-native (This Repo) |
Call the iOS SDK (globalThis.SCNView, UISegmentedControl, MTLCreateSystemDefaultDevice) and Android SDK (globalThis.android.graphics.*, android.os.*, java.lang.*) directly from TypeScript. |
• Zero native wrapper boilerplate when calling new iOS or Android SDK APIs. • 100% of the iOS SDK & Android classpath is immediately available in TypeScript. • Co-located with Angular signals: 3D rendering, native event handlers, and UI state live in the same TypeScript codebase and update instantly over Metro Fast Refresh. |
npm start # Start Metro bundler
npm run ios # Build and run on the iOS simulator
npm run android # Build and run on the Android emulator
npm test # Run Vitest unit tests in Node (no simulator needed)
npm run typecheck # Run Angular Native compiler & TypeScript typecheck