Skip to content

Angular guide

The Angular adapter drives the same native Fabric engine as React and Vue through a custom Renderer2/RendererFactory2 — Angular’s own framework-agnostic rendering seam. Bootstrap is DOM-less: no platform-browser, no zone.js. Angular renderer operations become engine mutations, then Fabric commits real native views.

Angular driving a native SymbioteNative demo

The examples/angular canary — the same component surface and engine as the React and Vue canaries, driven through Renderer2 instead.

import { Component } from '@angular/core';
import { Pressable, Text, View } from '@symbiote-native/angular';
import './app.css';
@Component({
selector: 'app-root',
standalone: true,
imports: [Pressable, Text, View],
template: `
<View class="root">
<Pressable (press)="increment()">
<Text class="label">Count is {{ count }}</Text>
</Pressable>
</View>
`,
})
export class App {
count = 0;
increment(): void {
this.count += 1;
}
}
app.css
.root { flex: 1; align-items: center; justify-content: center; }
.label { font-size: 24px; }

StyleSheet.create works identically if you’d rather keep style objects inline instead of a CSS file — see the Styling guide for both paths.

Angular is a standalone-components-only adapter:

  • every SymbioteNative component is standalone: true — import it directly in the consuming component’s imports array, there is no NgModule;
  • every component event is a real Angular @Output() EventEmitter(press)="onTap($event)", (longPress)="...", (hoverIn)="...", (valueChange)="...", (accessibilityAction)="...", and so on across Pressable, Button, Touchable*, Switch, TextInput, Modal, and every other component. An event nobody templates costs nothing: an unbound (longPress) still skips arming the long-press timer, the same as an absent callback prop used to. TextInput is the one component with two outputs for what React/Vue fold into one callback: (valueChange) stays text-only (an EventEmitter carries one value, and text-only keeps [(value)] two-way binding working) while (change) is a second, separate output for the raw native event;
  • the one permanent exception is the scroll family — onScroll, onScrollBeginDrag, onScrollEndDrag, onMomentumScrollBegin, onMomentumScrollEnd stay callback inputs ([onScroll]="handler") on ScrollView and the list components, because they can carry an Animated.event(...) marker for native-driven scroll and @Output() can’t bind an arbitrary value, only a template listener expression;
  • component selectors accept both a PascalCase alias and the symbiote-* prefix (selector: 'Pressable, symbiote-pressable'), so templates read like <Pressable>/<View>/<Text> while the underlying primitive hosts stay symbiote-view / symbiote-text / etc.;
  • imperative handles use @ViewChild(..., { read: ElementRef }) — the nativeElement is the SymbioteNative host node, not a DOM element;
  • change detection is zoneless: the adapter runs on Angular’s own ApplicationRef.tick(), wired in via the internal ɵprovideZonelessChangeDetectionInternal() helper rather than the public provideZonelessChangeDetection() (which assumes a platform-browser bootstrap this DOM-less renderer doesn’t use), so template updates batch the same way Vue’s requestCommit() does, without zone.js patching Hermes globals.

Read the exact API surface in the Angular API reference.