Text input

<text-input> is built to work with Signal Forms with no adapter code at all. Signal Forms' FormField directive looks for a value model on a value control, and <text-input>'s value is a model(), so <text-input [formField]="f.name" /> is the entire integration. It also works as a plain two-way binding without a form at all: [(value)].

Import
import { TextInput, InputAccessoryView } from '@ng-native/components';
Template
<text-input><input-accessory-view> once they are in the component's imports
<text-input placeholder="Name" [formField]="profile.name" />

The Signal Forms contract

<text-input> carries the whole control contract FormField writes to, not just value: disabled and readonly both turn editable off (disabled also wins the announced accessibility state); invalid and touched are published as the data-invalid and data-touched attributes, since nothing native reads them - they exist for a stylesheet; and touch fires from the native blur event, because there is no DOM blur for Signal Forms to listen to instead.

text-input {
  border-width: 1px;
  border-color: #3a3a42;
}
text-input[data-invalid][data-touched] {
  border-color: #ff6b6b;
}

The point of writing data-invalid/data-touched only once both are true is the usual one: a field an app has not touched yet should not look broken before anyone has had a chance to fill it in.

Controlled, as in React Native

Bind value and the field shows what your state says. A keystroke emits valueChange and changeText; whatever your state holds once the handler has run is what the field ends up showing. Take the text as typed, refuse it, or rewrite it:

<!-- Digits only: anything else leaves the field as it was. -->
<text-input [value]="pin()" (changeText)="isDigits($event) && pin.set($event)" />

<!-- Shows what is typed in capitals, as it is typed. -->
<text-input [value]="code()" (changeText)="code.set($event.toUpperCase())" />

When your state takes the text as typed, native already shows it and nothing is sent back. When it refuses or rewrites it, native is showing text your state does not hold, and a refused keystroke leaves your binding where it was, so there is no prop change to carry the correction. Once the change detection pass the keystroke started has finished, <text-input> compares value with the text native reported and, if they differ, sends React Native's setTextAndSelection command. A value set from code after the user has typed goes the same way.

Any binding makes the field controlled, one-way [value] included, so a field bound one way with nothing handling the change cannot be typed into, as in React Native. Decide in the handler, and do it synchronously: the comparison runs once the pass has finished, so a value that arrives later is compared as a refusal and then applied when it arrives. Leave value unbound and the field keeps what is typed. [formField] counts as a binding, and since a form takes every value the control gives it, typing into a form field sends nothing back.

That command carries the same counter as the rest of the typing protocol, the one React Native uses to keep fast typing safe: native reports each change with a counter, the component sends the latest one back, and native ignores anything older than what it already has. Without it, typing fast into a controlled field drops characters. None of this is something an app manages; it is what makes binding [(value)] straight to a signal safe. <switch>, on the switch page, is controlled the same way.

Keyboards and behavior

keyboardType picks the keyboard ('email-address', 'number-pad', 'phone-pad' and the rest); returnKeyType labels the return key, and submitBehavior decides what pressing it does ('submit', 'blurAndSubmit', or 'newline' - the default on a multiline field). multiline allows more than one line, secureTextEntry hides what is typed, autoCapitalize and autoCorrect control the usual typing assists, and maxLength is enforced natively so nothing flickers past the limit before being trimmed back.

A multiline field's text starts at the top, as in a <textarea>, on both platforms. Android's own default centres it, so there a multiline field commits textAlignVertical: 'top' unless the textAlignVertical input or a CSS vertical-align says otherwise. Both are Android-only: iOS always starts a multiline field at the top and centres a single-line one.

A rows attribute or binding on a multiline field with a line-height and no height of its own is its height: rows="3" is three lines, with the field's padding and border, as a browser sizes a <textarea rows="3">. A height written for the field wins, and a single-line field ignores rows. The engine reads it and does not send it on to the native field.

<text-input> exposes focus(), blur(), clear(), setSelection(start, end?) and isFocused() as methods, reachable through a template reference or nativeRef. (changeText) carries just the new string, matching React Native's onChangeText; (change), (focus), (blur), (submitEditing), (endEditing), (selectionChange), (keyPress) and (contentSizeChange) are the raw element events. (keyPress) names the key in $event.nativeEvent.key: Backspace, Enter, or the character typed (' ' for a space), on a device and in a browser alike. iOS reports no Enter from a single-line field, so use (submitEditing) for the return key there. (selectionChange) carries $event.nativeEvent.selection, a { start, end } that changes as the caret moves or a range is selected.

Bind [selection] to place the caret or select a range from your state: { start: 3 } is a caret after the third character, and { start: 1, end: 4 } a range. Whenever the bound selection differs from the one native last had, on the first render and after, it goes out with the same setTextAndSelection command, on iOS and Android alike. As in React Native the binding is controlled, so a caret the user moves goes back to the bound one; to let the user move it, update your state from (selectionChange).

On Android, cursorColor and selectionHandleColor follow selectionColor unless they are set, as React Native's do, so one selectionColor tints the caret, the handles and the highlight there, as it does on iOS.

(contentSizeChange) fires for a multiline field only, as React Native's does, and carries $event.nativeEvent.contentSize, the { width, height } of the text. Android reports it as the text changes. iOS reports it when the field itself changes size, as React Native's field does there: on the first layout, and as a field with no height of its own grows or shrinks with its text. A field held at a fixed height on iOS reports nothing while its text scrolls inside it, so a field that should grow with its text there leaves its height unset rather than setting it from this event.

To follow the field's own height as it grows, leave the height unset, cap it with max-height, and read (layout). It fires on both platforms whenever the field resizes, including when it reaches the cap. (contentSizeChange) is the size of the text, which leaves out the padding and goes on past the cap on Android.

<text-input
  multiline
  style="max-height: 160px"
  [(value)]="draft"
  (layout)="fieldHeight.set($event.nativeEvent.layout.height)"
/>
text-inputdirective@ng-native/components
import { TextInput } from '@ng-native/components';and add TextInput to the component's imports

A text field, wired for signal forms. Commits as TextInput , single or multi line by prop.

Inputs

disabled
unknown

Refuse input and announce as disabled. Wins over editable .

readonly
unknown

Refuse input, though still announced as enabled. Wins over editable .

invalid
boolean = false

Published as data-invalid ; nothing native reads it.

touched
boolean = false

Published as data-touched ; nothing native reads it.

multiline
unknown

Allow more than one line.

scrollEnabled
unknown

iOS: whether a multiline field scrolls its text; off, it grows with it instead.

editable
unknown

Set to false to make the field read-only.

placeholder
string

Shown while the field is empty.

placeholderTextColor
string

keyboardType
KeyboardType

Which keyboard to show.

returnKeyType
ReturnKeyType

The label on the return key.

submitBehavior
SubmitBehavior

What return does. Defaults to blurAndSubmit on one line and newline on many.

secureTextEntry
unknown

Hide what is typed, for passwords.

autoCapitalize
'none' | 'sentences' | 'words' | 'characters'

When the keyboard capitalises. Defaults to sentences .

autoCorrect
unknown

Auto-correct while typing. Defaults to true.

autoFocus
unknown

Focus the field as soon as it mounts.

maxLength
unknown

The most characters allowed, enforced natively so nothing flickers.

numberOfLines
unknown

Android: the height of a multiline field, in lines.

rows
unknown

How many lines of its line-height a multiline field is tall, where nothing sets its height, as a <textarea rows> is. Read by the engine, which sizes the field; never sent to native.

selection
{ readonly start: number; readonly end?: number }

The selected range, or the caret when end is left out or equals start . Sent to native whenever it differs from what native last had, so a caret the user moves goes back to it.

selectionColor
string

selectTextOnFocus
unknown

Select everything on focus.

caretHidden
unknown

contextMenuHidden
unknown

Disable the copy, paste and share menu.

textAlign
'left' | 'center' | 'right'

allowFontScaling
unknown

maxFontSizeMultiplier
unknown

inputAccessoryViewID
string

iOS: the nativeID of an <input-accessory-view> to dock above the keyboard.

clearButtonMode
'never' | 'while-editing' | 'unless-editing' | 'always'

iOS: when the clear button shows.

clearTextOnFocus
unknown

iOS: empty the field on focus.

enablesReturnKeyAutomatically
unknown

iOS: disable the return key while the field is empty.

keyboardAppearance
'default' | 'light' | 'dark'

iOS: the keyboard's colour scheme.

passwordRules
string

iOS: rules for a generated password, in Apple's Password Rules syntax.

spellCheck
unknown

iOS: underline misspellings. Follows autoCorrect by default.

smartInsertDelete
unknown

iOS: add and remove spaces around pasted and deleted words.

dataDetectorTypes
string | readonly string[]

iOS, multiline: turn phone numbers, links and addresses into taps.

textContentType
string

iOS: what the field holds, so the system can autofill it.

autoComplete
string

Android: the autofill hint.

underlineColorAndroid
string = 'transparent'

Android: the underline colour, transparent by default.

cursorColor
string

Android: the caret colour. Follows selectionColor by default, as RN's does.

selectionHandleColor
string

Android: the selection handle colour. Follows selectionColor by default, as RN's does.

textAlignVertical
'auto' | 'top' | 'bottom' | 'center'

Android: where text sits in a tall field.

importantForAutofill
'auto' | 'no' | 'noExcludeDescendants' | 'yes' | 'yesExcludeDescendants'

Android: whether autofill may fill this field.

showSoftInputOnFocus
unknown

Android: set to false to focus without raising the keyboard.

disableFullscreenUI
unknown

Android: keep the field in place in landscape rather than going full screen.

inlineImageLeft
string

Android: a drawable resource shown at the left of the field.

inlineImagePadding
unknown

textBreakStrategy
'simple' | 'highQuality' | 'balanced'

Android: the line-breaking strategy.

Two-way

value
string = ''

Outputs

changeText
string

The new text alone, on every native change.

touch
void

Focus left the field. What Signal Forms marks a field touched from.

Methods

focus(): void

Bring the keyboard up on this field. A view command, so it reaches native directly.

clear(): void

Empty the field.

setSelection(start: number, end = start): void

Move the caret, or select a range.

isFocused(): boolean

Whether this field currently has the keyboard.

Line height

A single-line field's line-height sets its height, as it sets an <input>'s in a browser: line height, padding and border, with the text centred in it. That holds on iOS as on Android, and a Tailwind font-size utility, which brings a line height with it, sizes a field the same way on both. An explicit height, or a larger min-height, still wins, and the text is centred in it on both. The line box grows with the system text size, as the text in it does, unless allowFontScaling is false, and no further than maxFontSizeMultiplier. In a multiline field the line height spaces the lines, as in a <textarea>.

Props a browser has no name for

Several inputs exist only because a native keyboard is a different object than an <input>: textContentType and autoComplete hint at autofill (a password manager, a one-time code) the way autocomplete does on the web, but with platform-specific values. passwordRules (iOS) constrains what a generated password looks like. On iOS, dataDetectorTypes makes recognized links and phone numbers tappable when multiline is true and editable is false. keyboardAppearance (iOS) sets the keyboard's own light or dark look independent of the app's theme, and showSoftInputOnFocus lets a field take focus (for a caret and a custom picker) without the system keyboard appearing at all.

Input accessory view

<input-accessory-view> docks a bar above the keyboard on iOS. Pair it with a <text-input> by giving the view a nativeID and the input the same string as inputAccessoryViewID. Android has no native counterpart to this and the element commits as a plain view, so content placed inside it simply sits inert rather than floating above the keyboard.

On iOS one bar goes with one field: when the bar appears, it attaches to the first <text-input> in the window whose inputAccessoryViewID names it, so a form with a bar under every field needs one <input-accessory-view>, with its own nativeID, per field.

A bar written inside a <scroll-view> is still drawn above the keyboard, outside the scroll view, so on iOS a tap on it that no control takes, on a disabled button or the space between two, leaves the keyboard up, whatever keyboardShouldPersistTaps says. React Native's own scroll view dismisses the keyboard for that tap. On Android the element is a plain view in the scroll view, and a tap on it counts as any other.

input-accessory-viewdirective@ng-native/components
import { InputAccessoryView } from '@ng-native/components';and add InputAccessoryView to the component's imports

A bar docked above the keyboard, iOS only. Pair it with a text input by giving this view a nativeID and the input the same inputAccessoryViewID .

Inputs

backgroundColor
string

The bar's background.

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.