Angular Native
DocsLearnExamplesGitHubSponsor
Build a habit tracker

6. A form for new habits

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.

Step 1 of 3

Bind a native field

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.