---
title: Manage Form-Driven State with ngrx-forms (Part 1)
description: Learn how to leverage NgRx, ngrx-forms and one-way data flow with your Angular forms.
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 21, 2021

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

 Learn how to leverage NgRx, ngrx-forms and one-way data flow with your Angular forms.

![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=)

If your [Angular](https://www.bitovi.com/why-build-with-angular) application already uses [NgRx](https://ngrx.io), you know the value of writing good reducers and using one-way data flow. However, that knowledge may not be enough to keep the biggest form in your app from being a rat’s nest of `FormGroup`s and related logic. Forget keeping components under 50 lines of code - your form logic alone blows past that.

Implementing the [ngrx-forms](https://ngrx-forms.readthedocs.io/) library can help with this problem. This open-source library helps you move form logic into the global NgRx state. Form data flows from form components to the store and back to components.

**Advantages**:

- One-way data flow like with NgRx, often easier to debug
- Lets you reuse reducers and other store logic
- Reduces component complexity
- Useful for forms with a lot of inputs or heavy business logic

Having worked on enterprise applications with large form components thousands of lines long, I can attest that ngrx-forms is a viable way to move complex form logic somewhere else and let the component focus on rendering markup and dispatching actions to the store.

**Disadvantages**:

- Storing local form state as global state
- Like NgRx, adds a layer of indirection
- Less popular package
- Docs are terse and only somewhat helpful

Storing local form state in the global state can have negative effects. It breaks encapsulation. It can allow form data to leak to other parts of the application and cause confusing, undesirable data flow. 

When choosing third-party tools to assist with development, popularity does not automatically equal quality, but you’re less likely to encounter a novel bug. It means your question likely has an existing answer on Stack Overflow.

To give a sense of scale for this library’s popularity, [@ngrx/store](https://www.npmjs.com/package/@ngrx/store) gets 378,000 downloads per week on NPM. ngrx-forms gets 4,000 a week.

![](https://lh5.googleusercontent.com/16YBLmn3Yrntz7YL9BI9LeDcoNIADb9rBlcMbrdnEISo_LXhzQH8ITpVZbqRig4bJXBNhLNf_mcfH9RtF3l6q1H-eLzopdhPivSaIhMqqJTKI9ySHCoI5NfPQ15yauGaCmfodAhI)

## How to Implement ngrx-forms

For this article, I’ll be using the Place My Order app built in Bitovi’s [Learn Angular tutorial](https://www.bitovi.com/academy/learn-angular.html). I’ve created [a fork with ngrx and ngrx-forms installed](https://github.com/kyle-n/angular-pmo/tree/ngrx-forms).

Place My Order is a simple sample app that lets users “order” from restaurants in one of a few cities. The version of the app built in the aforelinked tutorial uses Reactive Forms. While Reactive Forms are powerful, the order form component is too large. ngrx-forms will let us move that form logic and validation into the NgRx store.

Here is [reducers.ts](https://github.com/kyle-n/angular-pmo/blob/ngrx-forms/src/app/store/reducers.ts), a new file I made which declares our NgRx store and reducers:

```
// reducers.tsexport type GlobalState = {  orders: Array<Order>  mostRecentOrder?: Order;}export const initialState: GlobalState = {  orders: [],  mostRecentOrder: null,};export function reducer(  state = initialState,  action: any // normally this would be a union type of your action objects): GlobalState {  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};    default:      return state;  }}
```

First, to manage our form's state, we add it as a property on our existing NgRx state.

```
import { box, Boxed, createFormGroupState, FormGroupState } from 'ngrx-forms';export type GlobalState = {  orders: Array<Order>  mostRecentOrder?: Order;  orderForm: FormGroupState<OrderFormState>;}// shorthand to help TypeScript understand what we’re doingtype Override<T1, T2> = Omit<T1, keyof T2> & T2;type OrderFormState = Override<Order, {items: Boxed<Array<Item>>}>;const ORDER_FORM_ID = 'order_form_id';const initialOrderFormState = createFormGroupState<OrderFormState>(ORDER_FORM_ID, {  _id: '',  name: null,  address: null,  phone: null,  status: '',  items: box([])});export const initialState: GlobalState = {  orders: [],  mostRecentOrder: null,  orderForm: initialOrderFormState};
```

First, we add a new property to `GlobalState`, `orderForm`.

The order form group will have all the same properties as an `Order`: `_id`, `name`, `address`, `phone`, `status` and `items`. The only difference is in the form group, items is `Boxed<Array<Item>>`. Here’s why.

The Place My Order application uses the `pmo-menu-items` component to select items. The form control attached to `pmo-menu-items` will receive an array of item objects. 

ngrx-forms works out of the box with form control values as long as those values are JavaScript primitives like strings or numbers. However, if you want to use an object or array for your form control value in ngrx-forms, you must provide an initial value that is “boxed.” That way when we provide an empty array, ngrx-forms knows the array is our form control value and not indicating a `FormArray`.

Next, we update the reducer to update our form group when the user inputs data. `formGroupReducer` updates the value of form controls in state if `action` is an update to any of them. If the form group has changed at all, `formGroupReducer` returns a new object. Otherwise, it returns the previous form group state.

```
// reducers.tsimport { formGroupReducer } from 'ngrx-forms';export function reducer(  state = initialState,  action: any // normally this would be a union type of your action objects): GlobalState {  const orderForm = 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;  }}
```

Now, any time there is a change dispatched from the form or our TypeScript code, it will update the form group in the NgRx global state.

The next step is hooking up the global form state to our component. You select the desired form group from the global state, just like selecting non-form NgRx state properties.

```
// order.component.ts...  orderFormState$: Observable<FormGroupState<Order>>;  constructor(    private route: ActivatedRoute,     private restaurantService: RestaurantService,    private store: Store<GlobalState>  ) {     this.createdOrder = store.pipe(      select('order'),      select('mostRecentOrder')    );    // ngrx-forms FormGroup below    this.orderFormState$ = store.pipe(      select('order'),      select('orderForm')    );  }
```

Select the ngrx property for your form group and assign it `orderFormState$`. Now we bind it to our template:

```
<!-- order.component.html --><ng-container *ngIf="(orderFormState$ | async) as formState">      <h2>Order here</h2>      <form *ngIf="restaurant" [ngrxFormState]="formState" (ngSubmit)="onSubmit()">        <tabset>          <tab heading="Lunch Menu">            <ul class="list-group" >              <pmo-menu-items [data]="restaurant.menu.lunch" [ngrxFormControlState]="formState.controls.items"              ></pmo-menu-items>            </ul>          </tab>          <tab heading="Dinner menu">            <ul class="list-group" >              <pmo-menu-items [data]="restaurant.menu.dinner" [ngrxFormControlState]="formState.controls.items"></pmo-menu-items>            </ul>          </tab>        </tabset>        <div class="form-group">          <label class="control-label">Name:</label>          <input name="name" type="text" class="form-control" [ngrxFormControlState]="formState.controls.name">          <p>Please enter your name.</p>        </div>        <div class="form-group">          <label class="control-label">Address:</label>          <input name="address" type="text" class="form-control" [ngrxFormControlState]="formState.controls.address">          <p class="help-text">Please enter your address.</p>        </div>        <div class="form-group">          <label class="control-label">Phone:</label>          <input name="phone" type="text" class="form-control" [ngrxFormControlState]="formState.controls.phone">          <p class="help-text">Please enter your phone number.</p>        </div>              ...      </form>    </ng-container>
```

First, we grab the value of the form group using the `async` pipe and assign it an alias for easier reference. Next, attach form controls to template elements with the verbose `ngrxFormControlState` directive. You can log `formState` to see the object shape, it’s similar-ish to Reactive Forms `FormGroup`s.

And that’s it for basic setup! Typing in the template or selecting items will update the global state.

![ngrx-forms-1](https://www.bitovi.com/hs-fs/hubfs/angular-consulting/ngrx-forms-1.gif?width=3584&name=ngrx-forms-1.gif)

In [Part 2](https://www.bitovi.com/blog/manage-form-driven-state-with-ngrx-forms-part-2), we'll cover synchronous and asynchronous validation, as well as custom validators. 

[![Tag for open source](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) open source ](https://www.bitovi.com/blog/topic/open-source)[![Tag for javascript](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) javascript ](https://www.bitovi.com/blog/topic/javascript)[![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 reactive programming](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) reactive programming ](https://www.bitovi.com/blog/topic/reactive-programming)

 Previous Post

![](https://www.bitovi.com/hs-fs/hubfs/featured.jpg?height=117&name=featured.jpg) [ Creating a design system with Storybook ](https://www.bitovi.com/blog/creating-a-design-system-with-storybook)

  

 Next 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)

```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-1",
    "@type" : "WebPage",
    "description" : "Learn how to leverage NgRx, ngrx-forms and one-way data flow with your Angular forms."
  },
  "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:02:14 PM",
  "datePublished" : "2021-05-21 20:09:46",
  "description" : "Learn how to leverage NgRx, ngrx-forms and one-way data flow with your Angular forms.",
  "headline" : "Manage Form-Driven State with ngrx-forms (Part 1)",
  "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"
  }
}
```