Byte Limes
Published in

Byte Limes

Truly Reactive Forms in Angular: A Unique Approach

Photo by Luke Stackpoole on Unsplash

Reactive forms are not reactive enough in Angular. Especially when dealing with async observable data to pre-populate a form. Having to subscribe and call methods like setValue and patchValue seems very imperative and can be a recipe for data races and memory leaks. It can especially get messy when bulk editing an array of domain models. In this article we explore a different approach to reactive forms. Keep reading to know more

Observable of Forms!

That’s right! We can think of forms as a stream of UI input elements. Since forms can be thought of reactive setters that a user uses to set the data model of the view layer. As the data model itself is a stream, the form elements can be thought of as a stream of UI elements mapped from the underlying data stream.

Thinking of forms this way allows us to utilize composition before consumption of the data model in the template. Composition is a powerful and elegant tool in software engineering and mathematics. We can declare our Observable form on our component class like so:

form$: Observable<FormGroup>;
Photo by Ben White on Unsplash

Populating Observable Forms with Async Default values

In this way, populating the form with async data becomes easier than ever.
We can simply take the Observable of source data and map it to the form!

Explanation:

  • We create the ngOnInit life cycle hook to initialise our Observables. I like to initialise component properties in the ngOnInit hook because it lets me skim through all the class declarations and their type annotations in one sweep. You can initialise your Observables in the constructor if you like.
  • We create an observable of the post we want to edit by selecting it from the store. Here we are using NGRX as a reactive data store but you can use any async data source to make your observable (Http Service call for example).
  • We map the value emitted by the observable to a FormGroup using the FormBuilder injected into the component using angular’s dependency injection.

Usage in Template

Using this method we can delegate subscription management and observable unboxing to async pipe in the template.

Explanation:

  • We’ve wrapped our form in an ng-container. This allows us to unbox the Observable of the form as well as show a loading message in case our async data source has not loaded yet! Notice how easy and clean this is.
  • Then, we create a reference to our unboxed form using the as form; syntax along with the async pipe.
  • We create our form controls using the familiar reactive forms approach with form as our FormGroup
  • In the (click) output binding, we pass in the form object `onConfirm(form)` so that the handler has all the information it needs to build payload and delegate the update functionality to a service or the store.

Building Payloads

Building payload is easy too. Since we pass in our form group to the onConfirm method, We can use it to extract the data we require to delegate the update functionality to a service call or store effect.

<button 
[disabled]="!form.dirty || form.invalid"
(onConfirm)="onConfirm(form)"
>
Save
</button>

You can choose to pass in the form value instead of the formGroup object if you want. What’s more important is the way of thinking and approaching the problem rather than the finer details. You can set your own standards and conventions for your project/organisation.

onConfirm(form: FormGroup) {
const payload: Post = this.form.controls.post.value;
this.store.dispatch(postActions.UpdatePost());
}

Form Arrays

This technique is especially powerful when used with Form Arrays ( FormArray). Sometimes we wish to bulk edit a collection of domain models. In this case we can utilise `Observable Form Arrays` to compose our form elements and pre-populate them with existing data.

And you can use this observable of form array in your component’s template easily

Form Arrays in Template

Explanation:

  • Similar to the Observable Form Group example we saw above, we wrap our Observable in an ng-container to unbox, show loading message and store a local reference to the unboxed formArray
  • We use another ng-container to iterate over our pre-populated formGroups which can be accessed from formArray.controls. Remember, FormGroup , FormArray and FormControl are all extend AbstractControl and are hence AbstractControls themselves by Liskov Substitution . FormGroup and FormArray are boxing data structures which can contain many controls inside of them. They can be thought of as a Generic Data Structures even though it’s a shame they are not strongly typed as a generic. FormArray.controls is an iterable of FormGroup(s) or FormControl(s). In this example formArray.controls is an iterable of FormGroup because we have initialised it as an array of FormGroup(s)!
  • We are iterating over the form groups in our form array using *ngFor and creating form groups and controls as usual using the familiarReactive Forms approach

Observables of User Input

We can easily create Observables of user inputs as well in case we want to compose them for some sort of usage in templates or cross validation.

The shareReplay operator ensures that any subsequent subscriptions receive the last known value of the form input.

Conclusion

The greatest challenge of event driven programming is “staying up to date with the world”. Push based systems help us avoid challenges like race conditions and and data races. In this article we learnt to think of pre-populated form elements as a reactive stream of UI elements mapped from underlying data stream. We saw how we can initialize Form Groups and Form Arrays with pre-populated default values/existing values. We learnt how to use this in the template. Using this approach we can push our thinking into the more reactive realm and benefit from clean code and software composition.

If you want to learn more about reactive programming, check this book out.

Thank you for reading and I hope this helps you as it helped me.

I would love to know in the comments below if this was useful!

You can follow me on GitHub or Twitter
Happy Engineering!

--

--

--

We explore the depths of Software Engineering and learn tools and techniques to build scalable applications

Recommended from Medium

Is Node.js a Good Framework for E-Commerce?

The function map() in Javascript ES6

Useful React Hooks

The Power Of Chrome DevTools

A Simple Introduction to Web Workers in JavaScript

Javascript Concepts …

How to build a load more button with Vue.js and GraphQL

Unreal – FName non-preserved case sensitivitiy

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nivrith

Nivrith

Software Engineer living a polymath lifestyle in Adelaide, Australia

More from Medium

Control Value Accessor: Custom Form Components in Angular

RxJS based state management in Angular — Part I

Angular 13: Custom form validator

ViewportScroller service in angular

Browser with console mode on. Logs the scroll position