The form is Signal Forms, unchanged. What is new is the field it binds to. <text-input> commits
as the platform's own text field, a UITextField on iOS and an EditText on Android, so the
keyboard, the cursor, autocorrect and the return key all belong to the phone. The binding has to
meet that field halfway, and this lesson is about where it does.
Add a file, new-habit.ts, with the + after the file tabs. Give it a NewHabit component with a
form over one name, and bind a <text-input> to it:
import { Component, signal } from '@angular/core';
import { FormField, form } from '@angular/forms/signals';
import { TextInput, View } from '@ng-native/components';
@Component({
selector: 'new-habit',
imports: [FormField, TextInput, View],
template: `
<view class="mt-2 flex-row gap-2">
<text-input
class="flex-1 rounded-xl bg-white px-4 py-3 text-base text-zinc-900"
placeholder="New habit"
[formField]="habit.name"
/>
</view>
`,
})
export class NewHabit {
protected readonly model = signal({ name: '' });
protected readonly habit = form(this.model);
}
Then put <new-habit /> under the count in app.ts, above the <scroll-view>, and add
NewHabit to its imports.
There is no ControlValueAccessor here, and no adapter. FormField binds straight to the
control's value model, which <text-input> has. It has to be in the component's imports:
without it, [formField] binds nothing, and the field keeps what is typed while the form never
hears of it. Without a form, [(value)] binds a signal the same way; there is no ngModel, and no
DOM input to bind.
Any binding makes the field controlled, one-way [value] included: if the app does not take up
the change the field reports, the component puts the bound value back. The native field numbers
each change, and a value sent back carries the number it answers, so a stale update cannot
overwrite what has been typed since. When the form takes the text as typed, as it does here,
nothing is sent back at all.