ANGULAR AUTO-SAVE FORMS | RXJS OPERATORS
A brief guide to creating reactive and template-driven forms with RxJs and Angular Material
In this article, we will study how to create auto-saving forms in Angular. We will implement this feature with all its whistles and bells, such as toggling it on and off, displaying snack-bar notifications, and more.
In the process, we’re going to explore several RxJs operators. We will start by implementing the feature for reactive forms. Finally, we’ll see what changes are required for template-driven forms.
So, let’s get started!
Before we dive into the details, let’s take a quick look at the demo application.
The form has two input fields: the first and last name of the user. It has a “Save” button, which is enabled if the form is valid. Lastly, if auto-save is enabled, a loading bar will appear at the bottom of the form whenever the user changes any value. Here’s the code:
As you might have noticed, we use Angular Material. To install it, you just need to run:
ng add @angular/material.
Implementing the toggling of the auto-saving feature is quite easy.
First, we declare a
autoSaveEnabled in the
Then, we two-way bind that variable using
[(ngModel)]="autoSaveEnabled" on the
mat-slide-toggle element. Next, we pass the variable as an input to the
UserProfile component, which contains the form.
We handle value changes of the
autoSave input property by implementing the
OnChanges lifecycle hook. We define the
ngOnChanges method to enable or disable auto-saving based on the property’s value (more on that soon).
The question we need to ask is “when”? When do we want the form to be saved automatically?
We want this to happen when all of the following conditions hold:
- The auto-save is enabled
- The user changed a value
- The form is valid
Condition #1 has already been implemented in the previous section. The rest are implemented inside the
enableAutoSaving method. So, let’s take a look.
To auto-save the form when the user changes a value (condition #2), we need to subscribe to the form’s
From the official documentation,
valueChanges is “a multicasting observable that emits an event every time the value of the control changes, in the UI or programmatically.”
- On line 1, we define the
changesSubscriptionto hold the subscription. We do the assignment on line 6. This will let us unsubscribe later when we want to disable the feature.
- On lines 2 and 3, we define a
BehaviorSubjectand its respective observable for displaying or hiding the loading bar.
- On line 7, if the form is invalid, we filter and stop any emitted values (condition #3). There is no point in saving invalid values.
- On line 8, given that the user typed something that made the form valid, we emit
trueso the loading bar is displayed.
- On line 9, we use the
debounceTimeoperator with a time span of 1000ms (=1 second). The observable won’t emit unless the user stops typing for more than one second. We give the user time to react, pause, or think while typing.
- On lines 10–11, we use the
switchMapoperator, which is a higher-order mapping operator. It passes the user data from the form to the
saveUsermethod of the
UsersServiceand expects an observable. The method does return an observable, to which the
switchMapautomatically subscribes and unsubscribed.
This is called “inner observable” because it’s nested to another observable, the
valueChanges, which is called “outer observable.” Lastly, if another value is emitted before the HTTP request completes, then
switchMapcancels that request in favor of the new one.
- On line 12, we use the
finalizeoperator on the inner observable. When this observable completes (normally or by an error), we emit
falseto hide the loading bar.
- On line 15, we finally
saveUser method below, we’re using
catchError and return
EMPTY to avoid propagating any errors from the inner (
switchMap) observable to the outer (
If we allowed this to happen, the observable would complete, and we would need to resubscribe to listen for value changes.
Finally, when we need to disable the feature, we call the
disableAutoSaving method to unsubscribe from the
Unfortunately, we’re not done yet.
Assume the user has filled out the form, and is now valid. Then, they repeatedly press the “Backspace” button until one of the fields is empty. The form becomes invalid, but the last change — the one with the last remaining character — is not canceled.
Yeah, this is a minor detail, but these details make the difference!
We need to watch for form status changes and act accordingly. To do this, we need to subscribe to the
statusChanges observable of the form.
- On lines 1 and 4, we define and set the
statusSubscriptionvariable, just like we did with the
- On line 5, we use the
distinctUntilChangedoperator to allow only distinct status values to be emitted when the form changes from valid to invalid and vice versa.
- On line 6, we use the
pairwiseoperator to pair the previous and current emitted values. On line 7, we use the
tapoperator to use this pair.
- On lines 8–10, we call the
disableAutoSavingif the status of the form changed from
INVALID. This cancels the
changesSubscription, and thus no requests are dispatched. Problem fixed!
- On lines 12–15, we enable auto-saving if the form is
changesSubscriptionis closed, that is, if auto-saving was previously disabled.
- On line 14, we call
updateValueAndValidityexplicitly on the form. The first change that makes the form valid again won’t be auto-saved if we don’t. That’s because this specific change happened before calling the
enableAutoSaving, in which method we subscribe to the
- On line 17, we finally subscribe to the observable.
Finally, when we disable the auto-save feature, we must call the
disableStatusWatching method to unsubscribe from the
OK, now we’re done! 😅
We saw how this works with reactive forms. Can we auto-save template-driven forms? Of course, we can! We just have to make a few changes.
First, we need to import
FormsModule (if not already imported). By importing
NgForm directive becomes active on all
<form> tags. We don’t need to add any special selector.
But how are we going to listen for value and status changes? Well, we need to get a hold of the form.
We export the directive into a local template variable using
ngForm as the key. We also register child controls using
ngModel and the
Next, we get the reference of the form by using the
@ViewChild decorator in the component class. And that’s it! We can now use this reference and do what we’ve seen thus far.
Notice the weird syntax on line 19? No, this is not a typo.
NgForm directive creates a top-level
FormGroup instance and binds it to a form to track aggregate form value and validation status. This
FormGroup instance can be accessed through the
NgForm doesn’t provide the
updateValueAndValidity method or any other way to force update the form. So, we’re using this workaround.
You can find the source code in the previous links in a separate branch on both GitHub and StackBlitz.
In this article, we demonstrated how to create auto-saving forms in Angular. While at it, we used and explained several RxJs operators. Finally, we highlighted the differences between the reactive and template-driven forms implementations.
I hope you enjoyed this article and that you’ve learned something new. If you did, follow me and subscribe to my newsletter for more content like this.
Thanks for reading. Stay tuned for more.