Known limitations
Angular Native is an alpha. These known gaps have workarounds where available. See Angular Native compared for the distinction between implemented and verified features.
There is no DOM
Native apps have no browser DOM: document, window and DOM APIs do not work.
ElementRef.nativeElement is a retained engine node, not an HTMLElement, also exposed by
NativeRef in @ng-native/components for native commands. DomSanitizer and
NgOptimizedImage are unavailable; use <image> from @ng-native/components, which loads
assets through resolveAssetSource. mount() provides a DOCUMENT stub only to prevent
TransferState errors when creating a resource(); see
Bootstrapping. Do not use it as a
document. Element-based @defer triggers (on interaction, on hover, on viewport) have
native equivalents; see The renderer.
Workaround: use @ng-native/components for elements, Engine/HostEngine for imperative
native commands, and @ng-native/device for capabilities browsers expose through window or
navigator.
No @angular/animations
Angular's DI-based animation module has no native renderer. animate.enter and animate.leave
work through Renderer2 and a shim defining document, Node, AnimationEvent and
getComputedStyle before @angular/core loads. For other animations, see
Animation for AnimatedStyle and Reanimated worklets.
Workaround: animate.enter/animate.leave for mount/unmount transitions, AnimatedStyle or
Reanimated worklets (WorkletStyle, WorkletScroll) for anything driven by a gesture or computed
every frame. Both need the Metro preset's polyfills, which withAngularNative installs
automatically - see Metro.
i18n is runtime only
Angular i18n translates at runtime using template i18n and i18n- attributes, plurals and
selects, TypeScript $localize, localize-extract over the Metro bundle, and loadTranslations()
before mount. See Localization.
Loading a language after rendering does not update existing templates; switching language requires
an app reload; Transloco switches at runtime with no reload.
Metro has no build-time translation (localize-translate), and ng extract-i18n
cannot build native apps.
Plurals and selects work on iOS, Android and the web host.
A case of a plural or select holds text and interpolations. Angular keeps an element inside a case
only when it is on its HTML allowlist, which no native element is, so <text> inside a case is
left out along with its content.
Workaround: style a whole plural or select from the element around it. Install @babel/core@^7
next to @angular/localize, which depends on Babel 8: without it React Native's unranged Babel
peer can resolve to 8, and a worklets bundle then fails.
Angular DevTools does not attach
Angular DevTools requires a browser DOM component tree, so it cannot attach on a device. Use Pangular Inspector for components, signals and injectors, and Hermes, React Native's debugger and the renderer's committed tree for the rest.
No SSR or hydration
Neither native nor web supports server rendering or hydration. Native mount() targets a device's
Fabric UI manager; @ng-native/web's mount() targets a real document. For prerendered pages,
build prose statically and mount live examples on the client.
A server-rendered Angular web app can still host islands: the page renders on the server and hydrates, and each island renders in the browser alone.
HttpClient needs provideNativeHttpClient()
Angular 22's default fetch backend reads a response body only through response.body's
stream, so plain provideHttpClient() works only where the global fetch streams one. Expo's
runtime installs a fetch that does, but Metro runs that runtime only when something in the
bundle imports expo:
- The template, the generators and manual setup write
import 'expo';at the top ofsrc/main.ts, so debug and release builds both get Expo'sfetch. - In an app whose
src/main.tsdoes not importexpo, a debug build still gets Expo'sfetch, becausemount()reachesexpofor its reload hook in development, but a release build gets React Native's:whatwg-fetchover XHR, whoseResponsehas nobody. There every request resolves with a null body and no error, so the app works in debug and fails in release. - With
EXPO_PUBLIC_USE_RN_FETCHset to1ortrue, Expo's runtime leaves React Native'sfetchin place, so every build gets the one with nobody.
Use provideNativeHttpClient() from @ng-native/platform/http: it configures HttpClient with
withXhr(), which uses React Native's native XMLHttpRequest, upload progress included, whatever
the global fetch is. See HTTP requests for the signature and
an interceptor example.
Workaround: provideNativeHttpClient(...features) in mount()'s providers, never
provideHttpClient() on its own.
Errors raised outside the engine's event dispatch
Engine claims Fabric's single event-handler slot in its constructor. React's event plugins never
receive Angular Native view events, so handler errors cannot crash a screen push. The engine
catches errors from Angular listeners, responder handlers and direct Engine listeners, passes
them to the app's ErrorHandler, and continues bubbling. Two cases remain outside this protection:
requiring ReactFabric after mount() lets React reclaim the slot, and native errors are not
JavaScript errors.
Workaround: if a crash report shows a native stack with no JavaScript frames, check whether
anything required react-native/Libraries/Renderer after mount.
Section lists are vertical and upright
<virtual-list> supports separators (<ng-template virtualListSeparator>), sticky rows and a
sticky header. <section-list> covers SectionList, item and section separators included, but
lacks horizontal and inverted layouts and viewability events. Separators lack highlighted state. A
<section-list>'s row heights are fixed and include separators, as with getItemLayout; a
<virtual-list>'s can be measured instead. A virtual list holds its position with a scrollTo
after the commit rather than inside the native mount, so a correction made mid-fling lands where the
last scroll event said the list was.
On Android, <refresh-control> becomes the scroll view's parent, as in React Native. The scroll
view's inline layout style moves with it; class-based layout stays on the inner scroll view.
Hot reload has a few full-reload cases
Hot reload applies an edit to the running app, with its state and its current route, when the edit is to:
- a template or a style: inline,
templateUrlorstyleUrl, - a method, a getter or a lifecycle hook of a component, a service or a directive,
- a component's
importsorhost, - a function at the top of a module, exported or not, in the app's own source.
An edited method or function is on screen at once: a template or a computed that called it works
its value out again. An effect that called it does not run again.
A full reload follows an edit to:
- a field, a field initialiser or a constructor, which run only when an instance is created,
- a constant, a route table or anything else a module works out as it loads, and a function the module calls while it does,
- a function written as a constant,
const format = () => ..., that a class in the same file reads as it creates an instance, - a pipe, or a file that reads a
#privatemember, - a
selector, an input or an output, a directive's decorator, or a lifecycle hook added or removed, which Angular has already read, - a component whose template holds a router outlet, when the edit is to what it renders,
- a decorator's
providersorhostDirectivesnaming a class or a function from the same file.
A full reload makes every component and service again, and comes back on the page it left with
the pages that led to it beneath, each presented as it was: the router leaves its history with the
Metro dev server, which withAngularNative sets up to hold it. A round trip, one tab to another
and back, is left out. A reload from the dev menu starts on the first route.
External templates and stylesheets hot-swap in every component using them within the same
project, including ../ paths. Cross-package monorepo edits appear only after the component's own
file changes.
No imported value in a component's host or styles
A component's host and styles are evaluated at build time from the file they are in: a spread, a
constant or a function declaration in the same file works. @oxc-angular/vite compiles one file
at a time, so unlike Angular's own compiler it cannot follow a value imported from another file, and
the build fails naming the file and the import.
Workaround: for host, write the bindings in the same file, or share the behavior with a
directive in hostDirectives. For styles, put CSS shared between files in a .css file and list
it in styleUrl or styleUrls.