Device

@ng-native/device is the set of services that answer questions about the device and the operating system: how big the screen is, whether the user is in dark mode, how much of the screen the system has claimed, what the user has turned on in Settings, and whether the app is even in front of them right now. Everything here is a signal, not an observable and not a hook, so reading one in a template or a computed() is the same as reading any other piece of state.

Every service is providedIn: 'root' and built around an @Service() factory, so injecting one is the whole setup:

import { Component, inject } from '@angular/core';
import { Text } from '@ng-native/components';
import { ColorScheme } from '@ng-native/device';

@Component({
  selector: 'app-header',
  imports: [Text],
  template: `<text>{{ scheme.current() }}</text>`,
})
export class Header {
  protected readonly scheme = inject(ColorScheme);
}

A service nobody injects is never constructed, so there is no cost to importing the package and using only what a screen actually needs. Reach for a service here rather than reading React Native's own module directly - Dimensions, Appearance, AppState and the rest - because the service is what turns a callback-based API into a signal a computed() or a template can read without a manual subscription to tear down, and because it is what still works when there is no device: in a unit test, in mount's own bootstrap, in anything that imports this code without a phone underneath it.

Off a device

Every service here falls back to doing nothing when there is no React Native underneath it: no nativeFabricUIManager, no listeners, and a value that just sits at its neutral default. That is what makes the package importable by a test suite, by mount, and by anything else that runs this code somewhere other than a phone. ColorScheme reports light, Screen reports a zero-sized window, AppState reports active, and so on - nothing throws, and nothing waits forever for a platform that is not going to answer.

Most services also expose a SOURCE injection token, which a test overrides to drive the service without a device underneath it (SafeArea is the exception - it has no native module behind it, only report(), which <safe-area-provider> calls):

import { TestBed } from '@angular/core/testing';
import { ColorScheme } from '@ng-native/device';

TestBed.configureTestingModule({
  providers: [
    { provide: ColorScheme.SOURCE, useValue: { current: () => 'dark', subscribe: () => () => {} } },
  ],
});

The services

Screen

  • Screen - the window, the physical display, orientation, and the compact breakpoint.
  • Safe area - the insets SafeArea reports, fed by <safe-area-provider>.

Appearance

System

Feedback

  • Dialogs - the platform's own alert, confirmation, prompt, action sheet and toast.
  • Sharing - the system share sheet.
  • Vibration - the phone's motor.

Development

  • Dev menu - switches in the shake menu, gone in a release build.

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.