SECRET OF CSS

How to Create Auto-Saving Forms in Angular


ANGULAR AUTO-SAVE FORMS | RXJS OPERATORS

A brief guide to creating reactive and template-driven forms with RxJs and Angular Material

Auto-save forms in Angular
Photo created by author using Canva — Resources from the Angular PressKit

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.

Gif showing demo application with an auto-saving form in Angular.

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 boolean variable autoSaveEnabled in the AppComponent.

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:

  1. The auto-save is enabled
  2. The user changed a value
  3. 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 valueChanges observable.

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 changesSubscription to 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 BehaviorSubject and 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 true so the loading bar is displayed.
  • On line 9, we use the debounceTime operator 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 switchMap operator, which is a higher-order mapping operator. It passes the user data from the form to the saveUser method of the UsersService and expects an observable. The method does return an observable, to which the switchMap automatically 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 switchMap cancels that request in favor of the new one.
  • On line 12, we use the finalize operator on the inner observable. When this observable completes (normally or by an error), we emit false to hide the loading bar.
  • On line 15, we finally subscribe to valueChanges.

In the saveUser method below, we’re using catchError and return EMPTY to avoid propagating any errors from the inner (switchMap) observable to the outer (valueChanges) observable.

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 changeValues observable.

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 statusSubscription variable, just like we did with the changesSubscription.
  • On line 5, we use the distinctUntilChanged operator 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 pairwise operator to pair the previous and current emitted values. On line 7, we use the tap operator to use this pair.
  • On lines 8–10, we call the disableAutoSaving if the status of the form changed from VALID to 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 VALID and the changesSubscription is closed, that is, if auto-saving was previously disabled.
  • On line 14, we call updateValueAndValidity explicitly 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 changeValues observable.
  • 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 statusChanges observable.

OK, now we’re done! 😅

As always, you can find a working demo at this StackBlitz link or with the code in this GitHub repository.

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 FormsModule, the 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 name attribute.

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.

The 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 form property.

But 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.



News Credit

%d bloggers like this: