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 of src/main.ts, so debug and release builds both get Expo's fetch.
  • In an app whose src/main.ts does not import expo, a debug build still gets Expo's fetch, because mount() reaches expo for its reload hook in development, but a release build gets React Native's: whatwg-fetch over XHR, whose Response has no body. 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_FETCH set to 1 or true, Expo's runtime leaves React Native's fetch in place, so every build gets the one with no body.

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, templateUrl or styleUrl,
  • a method, a getter or a lifecycle hook of a component, a service or a directive,
  • a component's imports or host,
  • 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 #private member,
  • 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 providers or hostDirectives naming 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.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.