Skip to content

Angular guide

The Angular adapter drives the same native Fabric engine as React, Vue, Svelte, and Solid 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.

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

import { Component } from '@angular/core';
import { SYMBIOTE_ELEMENTS } from '@symbiote-native/angular';
import './app.css';
@Component({
selector: 'app-root',
standalone: true,
imports: [SYMBIOTE_ELEMENTS],
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:

  • the 19 primitive tags (view, pressable, text, text-input, switch, …) are Angular element directives, not components — SYMBIOTE_ELEMENTS (or an individual <Name>Element, e.g. ViewElement, PressableElement) declared in the component’s imports array is what lets ngtsc accept the lowercase tag with typed props; templates write the tag directly (<view>/<pressable>/<text>), matching every other adapter one-for-one;
  • every non-primitive SymbioteNative component (FlatList, SectionList, VirtualizedList, VirtualizedSectionList, KeyboardAvoidingView, …) is standalone: true and imported directly the same way — 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;
  • 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.