---
title: Manage Form-Driven State with ngrx-forms (Part 2)
description: Learn how to use ngrx-forms to do powerful synchronous and asynchronous form validation.
image: https://www.bitovi.com/hubfs/Banner_Images/sharon-mccutcheon-wRoyrBjSBzM-unsplash.jpg
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting)
- ![Project Management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project Management](https://www.bitovi.com/services/agile-project-management-consulting)
- ![Product Design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product Design](https://www.bitovi.com/services/product-design-consulting)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting)
- [Design systems](https://www.bitovi.com/services/axure-figma-migration)
- [React](https://www.bitovi.com/services/frontend/react-consulting)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about delivering products and solving problems.
  
  ](https://www.bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships)
- [![Academy](https://www.bitovi.com/hubfs/icon%20-%20academy%20(4).svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse or contribute to our community
  
  ](https://www.bitovi.com/open-source)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

[ Angular ](https://www.bitovi.com/blog/topic/angular) |  May 25, 2021

# Manage Form-Driven State with ngrx-forms (Part 2)

 Learn how to use ngrx-forms to do powerful synchronous and asynchronous form validation.

![Kyle Nazario](https://www.bitovi.com/hubfs/People/kyle-nazario.jpeg)

 Kyle Nazario

Share:

[![Twitter](https://www.bitovi.com/hubfs/limbo-generated/_astro/twitter-white.os3xLc3C_Z2nW4or.svg) ](https://twitter.com/intent/tweet?text=) [![Reddit](https://www.bitovi.com/hubfs/limbo-generated/imgs/icons/reddit.png) ](http://reddit.com/submit?url=)

This post is a continuation from [Part 1](https://www.bitovi.com/blog/manage-form-driven-state-with-ngrx-forms-part-1), where we set up a test project with [NgRx](https://ngrx.io) and [ngrx-forms](https://github.com/MrWolfZ/ngrx-forms) in our [Angular](https://www.bitovi.com/why-build-with-angular) application. For part 2, we will validate our form.

## Synchronous Validation

Say you want to make sure the user has filled out every field in the order form. To validate an ngrx-forms form group, you must add a validating function to the reducer. This differs from reactive forms, which require validators at form creation.

```
// reducers.tsimport { updateGroup, validate } from 'ngrx-forms';import { required } from 'ngrx-forms/validation';const validateOrderForm = updateGroup<OrderFormState>({  name: validate(required),  address: validate(required),  phone: validate(required),  items: validate(required)});export function reducer(  state = initialState,  action: any // normally this would be a union type of your action objects): GlobalState {  const orderForm = validateOrderForm(formGroupReducer(state.orderForm, action));  if (orderForm !== state.orderForm) {    state = {...state, orderForm};  }  switch (action.type) {    case ActionType.createOrderSuccess:      const orders = [...state.orders, action.order];      return {...state, orders, mostRecentOrder: action.order};    case ActionType.getOrdersSuccess:      return {...state, orders: action.orders};    case ActionType.clearOrderForm:      return {...state, orderForm: initialOrderFormState};    default:      return state;  }}
```

The new reducer validates all the inputs we list in `updateGroup()`. `required` is one of ngrx-form’s [built-in validators](https://ngrx-forms.readthedocs.io/en/master/user-guide/validation/).

If an input fails validation, the form control will have an error attached to it. Here’s how to access that error and react in the template:

If a form control passes validation, errors is an empty object.

```
<!-- order.component.html --><p *ngIf="formState.controls.items.errors.required" class="info text-error">Please choose an item</p>
```

## Custom validators

ngrx-forms comes with a lot of useful built-in validators, but sometimes you need something custom. Let’s add a validator so no one named Chris can use our app. Chrises, you know what you did.

```
// reducers.ts// syntax is odd but copied from ngrx-forms’ implementation of requiredinterface NoChrisValidationError<T> {  actual: T | null | undefined;}declare module 'ngrx-forms/src/state' {  interface ValidationErrors {    noChris?: NoChrisValidationError<any>  }}const noChris = (name: string | null | undefined): ValidationErrors => {  const errors: ValidationErrors = {};  if (name && name.toLowerCase() === 'chris') {    errors.noChris = 'No one named Chris!'  }  return errors;}
```

The important part is the custom validator function. The parameter should be typed as the form control value type or `null` or `undefined`. The function always returns a `ValidationErrors` object. If the parameter is invalid, we add an error key to the `ValidationErrors` object.

```
// from Angular source codeexport declare type ValidationErrors = {    [key: string]: any;};
```

To add the new validator to the form group, pass it as an additional argument to the `validate()` function for the desired form control. 

```
// reducers.tsconst validateOrderForm = updateGroup<OrderFormState>({  name: validate(required, noChris),  address: validate(required),  phone: validate(required),  items: validate(required)});
```

```
<!-- order.component.html --><p *ngIf="formState.controls.name.errors.noChris" class="info text-error">No Chrises allowed!</p>
```

## Asynchronous validators

An async validator is any validation that requires an async operation. For example, imagine a signup form for a website where users must have unique names. We might validate the `username` form control through an HTTP request to the server to see if that name is free. That would require an async validator.

Async validators are a little tougher to implement in ngrx-forms. After reading the docs, the easiest way I found is to write them as effects. 

[Effects](https://ngrx.io/guide/effects) are impure operations that take place before your reducers run. For example, our order form component might dispatch an action to create a new order. That action would be intercepted and POSTed to our API in an effect. If the POST request passes, the newly created order is passed to our reducer for storage in the state. If it fails, it isn’t. 

To demonstrate, let's install [google-libphonenumber](https://www.npmjs.com/package/google-libphonenumber), a popular open source library for validating phone numbers. We are going to check users’ phone numbers to see if they are valid in the US. 

We start with a function to validate phone numbers. google-libphonenumber actually runs synchronously, but this function will be async just to test async validators. 

```
// phone-validator.tsimport {PhoneNumberUtil} from 'google-libphonenumber';const phoneUtil = PhoneNumberUtil.getInstance();async function isValidUSNumber(number: string): Promise<boolean> {  try {    const usNumber = phoneUtil.parse(number, 'US');    return phoneUtil.isValidNumberForRegion(usNumber, 'US');  } catch {    return false;  }}export default isValidUSNumber;
```

Now, in [effects.ts](https://github.com/kyle-n/angular-pmo/blob/ngrx-forms/src/app/store/effects.ts):

```
// effects.ts@Injectable()export class OrderEffects {  @Effect()  submitOrder$ = this.actions$.pipe(    ofType<ReturnType<typeof createOrder>>(ActionType.createOrder),    mergeMap(action => {      return this.orderService.createOrder(action.order).pipe(        map((newOrder: Order) => ({ type: ActionType.createOrderSuccess, order: newOrder}))      )    })  );  @Effect()  getOrders$ = this.actions$.pipe(    ofType(ActionType.getOrders),    mergeMap(() => this.orderService.getOrders().pipe(      map((response: any) => ({ type: ActionType.getOrdersSuccess, orders: response.data }))    ))  );  constructor(    private actions$: Actions,    private orderService: OrderService  ) {}}
```

We’ll add a new effect that listens for form control updates to our phone number input. 

```
// effects.tsimport { Actions, Effect, ofType } from '@ngrx/effects';import {ClearAsyncErrorAction, SetAsyncErrorAction, SetValueAction, StartAsyncValidationAction} from 'ngrx-forms';import { from } from 'rxjs';import isValidUSNumber from '../phone-validator';...  @Effect()  validatePhoneNumber$ = this.actions$.pipe(    ofType(SetValueAction.TYPE),    filter((formControlUpdate: SetValueAction<string>) => formControlUpdate.controlId === 'order_form_id.phone'),    switchMap(formControlUpdate => {      const errorKey = 'validPhone'      return from(isValidUSNumber(formControlUpdate.value)).pipe(        map(validPhone => {          return validPhone ? new ClearAsyncErrorAction(formControlUpdate.controlId, errorKey) : new SetAsyncErrorAction(formControlUpdate.controlId, errorKey, true);        }),        startWith(new StartAsyncValidationAction(formControlUpdate.controlId, errorKey))      );    })  );
```

Let’s break down that operator chain:

- We listen to `this.actions$` to see actions as they come into the store.
- We filter out all actions except those of type `SetValueAction`, which is ngrx-forms updating some form control.
- We filter all ngrx-forms updates except those targeting the phone form control on our order form group.
- We create a new `Observable` representing an asynchronous validation of the new form control value.
- If the form control value is valid, send a new action to the store clearing any phone validation error stored on the form control. 
- If it is invalid, set a new async error on that form control. Async errors are like sync errors, but they are referenced slightly differently in the template. 
- While the form control is being asynchronously validated, we tell the store that an async validation has started.

Basically, when the store is told to update the phone form control, we tell the store we are asynchronously checking its validity. When that check completes, we tell the store if it passed. 

Last step: In the template, we display async errors if they exist.

```
<!-- order.component.html --><p *ngIf="formState.controls.phone.errors.$validPhone" class="info text-error">Invalid phone number</p>
```

Async errors on form controls are represented with a “$” prefix on form control objects. 

## Conclusion

That’s validation in ngrx-forms! A small but powerful library, especially if your application is already deeply invested in NgRx.

[![Tag for tutorial](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) tutorial ](https://www.bitovi.com/blog/topic/tutorial)[![Tag for ngrx](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) ngrx ](https://www.bitovi.com/blog/topic/ngrx)[![Tag for rxjs](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) rxjs ](https://www.bitovi.com/blog/topic/rxjs)[![Tag for reactive programming](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) reactive programming ](https://www.bitovi.com/blog/topic/reactive-programming)

 Previous Post

![person-with-megaphone](https://www.bitovi.com/hs-fs/hubfs/person-megaphone-dark-crop-social.jpg?height=117&name=person-megaphone-dark-crop-social.jpg) [ Understand Declarative vs. Imperative Code using Array Functions ](https://www.bitovi.com/blog/understand-declarative-vs-imperative-code-using-array-functions)

  

 Next Post

![](https://www.bitovi.com/hs-fs/hubfs/rxjs7.png?height=117&name=rxjs7.png) [ What’s New in RxJS 7: Small Bundles and Big Changes to share() ](https://www.bitovi.com/blog/whats-new-in-rxjs-7-small-bundles-and-big-changes-to-share)

```json
{
  "@context" : "http://schema.org",
  "@type" : "Organization",
  "address" : {
    "@type" : "PostalAddress",
    "addressCountry" : "United States",
    "addressLocality" : "Libertyville",
    "addressRegion" : "IL",
    "postalCode" : "60048",
    "streetAddress" : "1134 Pine Tree Lane "
  },
  "alternateName" : "Bitovi",
  "areaServed" : {
    "@type" : "GeoCircle",
    "geoMidpoint" : {
      "@type" : "GeoCoordinates",
      "latitude" : "41.8781",
      "longitude" : "87.6298"
    },
    "geoRadius" : "5000 km"
  },
  "description" : "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
  "email" : "contact@bitovi.com",
  "image" : "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
  "logo" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
  "mainEntityOfPage" : {
    "@id" : "https://www.bitovi.com/blog/manage-form-driven-state-with-ngrx-forms-part-2",
    "@type" : "WebPage",
    "description" : "Learn how to use ngrx-forms to do powerful synchronous and asynchronous form validation."
  },
  "naics" : "541511",
  "name" : "Bitovi Web App Consulting",
  "sameAs" : [ "https://www.facebook.com/BitoviLLC/", "https://twitter.com/bitovi", "https://www.linkedin.com/company/bitovi" ],
  "telephone" : "312-620-0386",
  "url" : "http://bitovi.com"
}
```

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Kyle Nazario"
  },
  "dateModified" : "January 28, 2022, 6:03:19 PM",
  "datePublished" : "2021-05-25 21:51:21",
  "description" : "Learn how to use ngrx-forms to do powerful synchronous and asynchronous form validation.",
  "headline" : "Manage Form-Driven State with ngrx-forms (Part 2)",
  "image" : {
    "@type" : "ImageObject",
    "url" : "https://www.bitovi.com/hubfs/Banner_Images/sharon-mccutcheon-wRoyrBjSBzM-unsplash.jpg"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg"
    },
    "name" : "Bitovi"
  }
}
```