Official Documentation View Source on Github

Highlights

  1. Use FormBuilder and bind it to the form with the [formGroup] attribute.
  2. Use the ngxsForm attribute to bind the form to the state.
  3. Use the formControlName attribute to bind each field to its form control.
  4. Create one getter per form control for easier access.
  5. Access each field validation errors through its getter.
  6. Access the form validation state through the form.

// app/simple-form/user.ts

export interface User {
  id: number;
  name: string;
  birthdate: Date;
  favoriteColor?: string;
}
<!-- app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.component.html -->

<h1>User Form (NGXS with form-plugin)</h1>

<mat-card>
  <form
    ngxsForm="userFormNgxsPlugin.userForm"
    [formGroup]="userForm"
    (ngSubmit)="submit()"
  >
    <mat-form-field>
      <mat-label>Name</mat-label>
      <input matInput required name="name" formControlName="name" />
      <mat-error *ngIf="name?.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <mat-form-field>
      <mat-label>Birth date</mat-label>
      <input
        matInput
        required
        name="birthdate"
        formControlName="birthdate"
        [matDatepicker]="picker"
      />
      <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
      <mat-datepicker #picker></mat-datepicker>
      <mat-error *ngIf="birthdate?.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <mat-form-field>
      <mat-label>Favorite color</mat-label>
      <mat-select name="favoriteColor" formControlName="favoriteColor">
        <mat-option *ngFor="let color of colors" [value]="color">{{
          color
        }}</mat-option>
      </mat-select>
    </mat-form-field>
    <div class="form-buttons">
      <button
        mat-raised-button
        type="submit"
        color="primary"
        [disabled]="(loading$ | async) || !userForm.valid"
      >
        Submit
      </button>
    </div>
  </form>
</mat-card>

<h2>User (JSON)</h2>

<pre
  >{{ user$ | async | json }}
</pre>

<h2>Form state (JSON)</h2>

<pre
  >{{ userFormState$ | async | json }}
</pre>
// app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.component.ts

import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';
import { User } from 'src/app/simple-form/user';
import { UserFormNgxsPlugin } from 'src/app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.actions';
import { UserFormNgxsPluginSelectors } from 'src/app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.selectors';
import { UserForm } from 'src/app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.state';

@Component({
  selector: 'app-user-form-ngxs-plugin',
  templateUrl: './user-form-ngxs-plugin.component.html',
})
export class UserFormNgxsPluginComponent {
  readonly colors = ['Red', 'Green', 'Blue'];
  readonly userForm = this.formBuilder.group({
    name: [''],
    birthdate: [''],
    favoriteColor: [''],
  });

  @Select(UserFormNgxsPluginSelectors.form)
  userFormState$!: Observable<UserForm>;

  @Select(UserFormNgxsPluginSelectors.model)
  user$!: Observable<User>;

  @Select(UserFormNgxsPluginSelectors.loading)
  loading$!: Observable<boolean>;

  get name() {
    return this.userForm.get('name');
  }

  get birthdate() {
    return this.userForm.get('birthdate');
  }

  constructor(
    private readonly formBuilder: FormBuilder,
    private readonly store: Store,
    private readonly snackBar: MatSnackBar
  ) {}

  submit(): void {
    this.store
      .dispatch(new UserFormNgxsPlugin.Submit())
      .pipe(withLatestFrom(this.user$))
      .subscribe(([, { id }]) => {
        this.snackBar.open(`User saved with ID #${id}`, 'Close');
      });
  }
}
// app/simple-form/user.service.ts

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { User } from 'src/app/simple-form/user';

export interface UserSaveResponse {
  id: number;
}

@Injectable({
  providedIn: 'root',
})
export class UserService {
  save(user: User): Observable<UserSaveResponse> {
    console.log('Save:', user);
    return of({
      id: 1,
    }).pipe(delay(1000));
  }
}
// app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.state.ts

import { Injectable } from '@angular/core';
import { Action, State, StateContext } from '@ngxs/store';
import { finalize, tap } from 'rxjs/operators';
import { User } from 'src/app/simple-form/user';
import { UserFormNgxsPlugin } from 'src/app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.actions';
import { UserService } from 'src/app/simple-form/user.service';

export interface UserForm {
  model: User;
  status: string;
  dirty: boolean;
}

export interface UserFormNgxsPluginStateModel {
  userForm: UserForm;
  loading: boolean;
}

@State<UserFormNgxsPluginStateModel>({
  name: 'userFormNgxsPlugin',
  defaults: {
    userForm: {
      model: {
        id: 0,
        name: '',
        birthdate: new Date(),
        favoriteColor: '',
      },
      status: 'INVALID',
      dirty: false,
    },
    loading: false,
  },
})
@Injectable()
export class UserFormNgxsPluginState {
  constructor(private readonly userService: UserService) {}

  @Action(UserFormNgxsPlugin.Submit)
  submit(context: StateContext<UserFormNgxsPluginStateModel>) {
    const state = context.getState();
    context.patchState({
      loading: true,
    });
    return this.userService.save(state.userForm.model).pipe(
      tap(({ id }) =>
        context.patchState({
          userForm: {
            ...state.userForm,
            model: {
              ...state.userForm.model,
              id,
            },
          },
        })
      ),
      finalize(() =>
        context.patchState({
          loading: false,
        })
      )
    );
  }
}
// app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.actions.ts

export namespace UserFormNgxsPlugin {
  export class Submit {
    static readonly type = '[UserFormNgxsPlugin] Submit';
  }
}
// app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.selectors.ts

import { Selector } from '@ngxs/store';
import { User } from 'src/app/simple-form/user';
import {
  UserForm,
  UserFormNgxsPluginState,
  UserFormNgxsPluginStateModel,
} from 'src/app/simple-form/user-form-ngxs-plugin/user-form-ngxs-plugin.state';

export class UserFormNgxsPluginSelectors {
  @Selector([UserFormNgxsPluginState])
  static form({ userForm }: UserFormNgxsPluginStateModel): UserForm {
    return userForm;
  }

  @Selector([UserFormNgxsPluginSelectors.form])
  static model({ model }: UserForm): User {
    return model;
  }

  @Selector([UserFormNgxsPluginState])
  static loading({ loading }: UserFormNgxsPluginStateModel): boolean {
    return loading;
  }
}