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
compactbreakpoint. - Safe area - the insets
SafeAreareports, fed by<safe-area-provider>.
Appearance
- Colour scheme - light or dark, as the user set it.
- Accessibility - screen reader, reduced motion, bold text and font scale.
- Direction - left-to-right or right-to-left.
System
- Status bar - a stack of claims on style, visibility and colour.
- Keyboard - height, position and animation timing.
- Hardware back - claiming Android's hardware back button.
- App state - whether the app is in front of the user.
- Deep links - the launch url and every link that arrives after.
- Android permissions - runtime permissions, granted outright on iOS.
- Layout animation - animating a layout change CSS cannot express.
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.