# Create your form

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

Formisch consists of functions, directives and methods. To create a form you use the [`injectForm`](/angular/api/injectForm.md) function.

## Form function

The [`injectForm`](/angular/api/injectForm.md) function initializes and returns the store of your form. The store contains the state of the form and can be used with other Formisch functions, directives and methods to build your form.

Like Angular's own `inject`, it must be called in an injection context — a field initializer or the constructor of your component.

```ts
import { Component } from '@angular/core';
import { FormischForm, injectForm } from '@formisch/angular';
import * as v from 'valibot';

const LoginSchema = v.object({
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
});

@Component({
  selector: 'app-login',
  imports: [FormischForm],
  template: `
    <form [formischForm]="loginForm" [formischSubmit]="handleSubmit">
      <!-- Form fields will go here -->
    </form>
  `,
})
export class LoginComponent {
  readonly loginForm = injectForm({ schema: LoginSchema });

  readonly handleSubmit = (output: v.InferOutput<typeof LoginSchema>) => {
    console.log(output);
  };
}
```

The [`[formischForm]`](/angular/api/formischForm.md) directive is a thin layer around the native `<form>` element. It validates the input on submit, focuses the first invalid field, registers the form element for the [`submit`](/methods/api/submit.md) method, and disables native browser validation by adding `novalidate`. Its companion input `[formischSubmit]` receives your submit handler as a function reference instead of calling it in the template, so that Formisch can await the returned promise and keep `loginForm.isSubmitting()` up to date. Declare the handler as an arrow function property — a regular class method would lose its `this`.

### Configuration options

The [`injectForm`](/angular/api/injectForm.md) function accepts a configuration object with the following options:

- `schema`: Your Valibot schema that defines the form
- `initialInput`: Initial values for your form fields (optional)
- `emptyInput`: The empty value each field type starts at when a required field has no initial input (optional, defaults to `{ string: '' }`)
- `validate`: When validation first occurs (optional, defaults to `'submit'`)
- `revalidate`: When revalidation occurs after initial validation (optional, defaults to `'input'`)

```ts
readonly loginForm = injectForm({
  schema: LoginSchema,
  initialInput: {
    email: 'user@example.com',
  },
  validate: 'initial',
  revalidate: 'input',
});
```

Formisch tracks two inputs for every field: the **initial input** (baseline for dirty tracking) and the **current input** (what the user is editing). In many apps, the initial input represents the server state while the current input represents the client state.

`isDirty()` becomes `true` when a field's current input differs from its initial input. Use [`setInput`](/methods/api/setInput.md) to update the current input (client state), and use [`reset`](/methods/api/reset.md) to update the initial input (baseline) when your server data changes or is refreshed.

### Empty input

By default, a required string field starts as an empty string (`''`) instead of `undefined`, matching an empty text input. This way an empty field shows the validation message you defined for it (for example from `v.nonEmpty()`) without you setting an `initialInput` for every field.

You can configure the empty value per field type, or opt out by setting a type to `undefined`:

```ts
readonly loginForm = injectForm({
  schema: LoginSchema,
  emptyInput: {
    string: '', // the default
    number: 0, // required numbers start at 0 instead of undefined
    boolean: false, // checkboxes start unchecked
  },
});
```

Optional and nullable fields are never affected and keep starting as `undefined`, since they accept it. The supported types are `string`, `number`, `boolean` and `date`, and the default is `{ string: '' }`.

## Multiple forms

When a component contains multiple forms, you can create separate form stores for each one:

```ts
@Component({
  selector: 'app-auth',
  imports: [FormischForm],
  template: `
    <form [formischForm]="loginForm" [formischSubmit]="handleLogin">
      <!-- … -->
    </form>
    <form [formischForm]="registerForm" [formischSubmit]="handleRegister">
      <!-- … -->
    </form>
  `,
})
export class AuthComponent {
  readonly loginForm = injectForm({ schema: LoginSchema });
  readonly registerForm = injectForm({ schema: RegisterSchema });

  readonly handleLogin = (output: v.InferOutput<typeof LoginSchema>) => {
    console.log(output);
  };

  readonly handleRegister = (output: v.InferOutput<typeof RegisterSchema>) => {
    console.log(output);
  };
}
```

If you need a multi-step form (wizard), see the [multi-step form discussion](https://github.com/open-circle/formisch/discussions/108) for patterns and approaches.

## Next steps

Now that you know how to create a form, continue to the [add form fields](/angular/guides/add-form-fields.md) guide to learn how to connect your input elements to the form using the [`*formischField`](/angular/api/formischField.md) directive.
