# Input components

> This document is the Markdown version of [formisch.dev/angular/guides/input-components/](https://formisch.dev/angular/guides/input-components/). For the complete documentation index, see [llms.txt](https://formisch.dev/llms.txt).

To make your code more readable, we recommend that you develop your own input components if you are not using a prebuilt UI library. There you can encapsulate logic to display error messages, for example.

> If you're already a bit more experienced, you can use the input components we developed for our [playground](/playground/login/) as a starting point. You can find the code in our GitHub repository [here](https://github.com/open-circle/formisch/tree/main/playgrounds/angular/src/components).

## Why input components?

Currently, your fields might look something like this:

```angular-html
<ng-container *formischField="['email'] of loginForm; let field">
  <div>
    <label [for]="field.name()">Email *</label>
    <input
      [id]="field.name()"
      [formischControl]="field"
      type="email"
      required
    />
    @if (field.errors(); as errors) {
      <div>{{ errors[0] }}</div>
    }
  </div>
</ng-container>
```

If CSS and a few more functionalities are added here, the code quickly becomes confusing. In addition, you have to rewrite the same code for almost every form field.

Our goal is to develop a `TextInputComponent` so that the code ends up looking like this:

```angular-html
<app-text-input
  *formischField="['email'] of loginForm; let field"
  [field]="field"
  type="email"
  label="Email"
  [required]="true"
/>
```

## Create an input component

In the first step, you create a new file for the `TextInputComponent`. The component takes the field store as an input and passes it on to the [`[formischControl]`](/angular/api/formischControl.md) directive.

Keep the component generic over the schema and the field path. The field store is invariant because of its `setInput` method, so a component that widens the input to the default `FieldStore` cannot accept a concrete field.

```ts
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import {
  type FieldStore,
  FormischControl,
  type FormSchema,
  type RequiredPath,
} from '@formisch/angular';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-text-input',
  imports: [FormischControl],
  template: `…`,
})
export class TextInputComponent<
  TSchema extends FormSchema = FormSchema,
  TFieldPath extends RequiredPath = RequiredPath,
> {
  readonly field = input.required<FieldStore<TSchema, TFieldPath>>();
  readonly type = input<string>('text');
  readonly label = input<string>();
  readonly placeholder = input<string>();
  readonly required = input<boolean>(false);
}
```

### Template code

After that, you can add the template code. Use `field().name()` as the `id` so that the label and the error message reference the input element.

```ts
template: `
  @if (label(); as label) {
    <label [for]="field().name()">
      {{ label }} @if (required()) { <span>*</span> }
    </label>
  }
  <input
    [id]="field().name()"
    [type]="type()"
    [placeholder]="placeholder() ?? ''"
    [attr.aria-errormessage]="
      field().errors() ? field().name() + '-error' : null
    "
    [formischControl]="field()"
  />
  @if (field().errors(); as errors) {
    <div [id]="field().name() + '-error'">{{ errors[0] }}</div>
  }
`;
```

You don't need to set `aria-invalid` yourself. The [`[formischControl]`](/angular/api/formischControl.md) directive keeps it in sync with the field's errors.

### Next steps

You can now build on this code and add CSS, for example. You can also follow the procedure to create other components such as `CheckboxComponent`, `SliderComponent`, `SelectComponent` and `FileInputComponent`.

### Final code

Below is an overview of the entire code of the `TextInputComponent`.

```ts
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import {
  type FieldStore,
  FormischControl,
  type FormSchema,
  type RequiredPath,
} from '@formisch/angular';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-text-input',
  imports: [FormischControl],
  template: `
    @if (label(); as label) {
      <label [for]="field().name()">
        {{ label }}
        @if (required()) {
          <span>*</span>
        }
      </label>
    }
    <input
      [id]="field().name()"
      [type]="type()"
      [placeholder]="placeholder() ?? ''"
      [attr.aria-errormessage]="
        field().errors() ? field().name() + '-error' : null
      "
      [formischControl]="field()"
    />
    @if (field().errors(); as errors) {
      <div [id]="field().name() + '-error'">{{ errors[0] }}</div>
    }
  `,
})
export class TextInputComponent<
  TSchema extends FormSchema = FormSchema,
  TFieldPath extends RequiredPath = RequiredPath,
> {
  readonly field = input.required<FieldStore<TSchema, TFieldPath>>();
  readonly type = input<string>('text');
  readonly label = input<string>();
  readonly placeholder = input<string>();
  readonly required = input<boolean>(false);
}
```

## Next steps

Now that you know how to create reusable input components, continue to the [handle submission](/angular/guides/handle-submission.md) guide to learn how to process form data when the user submits the form.
