Handling unsaved changes and save through ionic/angular reactive forms
In Ionic Angular, handling unsaved changes in reactive forms is crucial to enhancing user experience. It ensures that users are prompted before accidentally navigating away or losing important form data. There are multiple techniques you can apply to detect unsaved changes in reactive forms, each of which serves a specific use case.
Why monitor unsaved changes?
Users may forget to save their progress before navigating away from a form.
Without detecting unsaved changes, any modified information can be lost.
Losing unsaved data can lead to user frustration and a poor user experience.
By detecting changes, you can prompt users to save their work, helping prevent data loss.
This ensures smoother interactions and a more user-friendly experience within your app.
Implementing Change Detection in Reactive Forms
- initialize an ionic app. Install Ionic CLI :
npm install -g @ionic/cli
- Create a New Ionic Angular App and navigate to project directory :
ionic start unsavedChangesApp blank --type=angular
cd myReactiveFormApp
- Generate page for implementing reactive forms module and import ReactiveFormsModule :
ionic generate page form1
// import module in standalone component
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-form1',
templateUrl: './form1.page.html',
styleUrls: ['./form1.page.scss'],
standalone: true,
imports: [
ReactiveFormsModule,
],
})
export class form1 implements OnInit, {}
- Create a file called UnsavedChanges and export it :
export interface UnsavedChanges {
isDirty: () => boolean;
handleUnsavedChanges: () => () => void | Promise<void>;
hasNonEmptyValue: () => boolean;
}
- Create a modal service called unsaved modal service and use the alert controller to popup the modal :
import { Injectable } from '@angular/core';
import { AlertController } from '@ionic/angular/standalone';
@Injectable({
providedIn: 'root',
})
export class UnsavedModalService {
constructor(private alertController: AlertController) {}
async presentAlert(
handleUnsavedChange: () => void | Promise<void>
): Promise<boolean> {
return new Promise<boolean>(async (changeRoute) => {
const alert = await this.alertController.create({
header: 'Unsaved Changes',
message: 'You have unsaved changes. Do you really want to save?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
handler: () => {
changeRoute(false);
},
},
{
text: 'Save',
handler: async () => {
await handleUnsavedChange();
changeRoute(true);
},
},
],
});
await alert.present();
});
}
}
- Now create a universal service called UnsavedChangesDetect and use those service in it :
import { Injectable } from '@angular/core';
import { UnsavedChanges } from './unsaved-changes';
import { UnsavedModalService } from './unsaved-modal.service';
@Injectable({
providedIn: 'root',
})
export class UnsavedChangesDetectService {
constructor(private unsavedModalService: UnsavedModalService) {}
async canDeactivate(component: UnsavedChanges): Promise<boolean> {
const isDirty = component.isDirty();
if (isDirty) {
const result = await this.unsavedModalService.presentAlert(
component.handleUnsavedChanges()
);
return result;
}
return true;
}
}
This will create a guard for unsaved changes in a particular component.
Now in a page where we have a form declared by angular reactive forms, we will create 3 methods which will give us the mechanism of handling unsaved changes :
// Detect whether the form value has been changed or has been touched
isDirty(): boolean {
return this.newForm.dirty && this.hasNonEmptyValue();
}
// This will handle the unsaved changes for a particular save method.
// It will trigger the save method for your form which is already been declared
handleUnsavedChanges(): () => void | Promise<void> {
return () => {
this.onSave();
};
}
// It will check for undefined and non empty values.
hasNonEmptyValue(): boolean {
const formValues = this.newForm.value;
return Object.values(formValues).some(
(value) => value !== null && value !== undefined && value !== ''
);
}
Now we will see the uses of these methods in the page which we had already created :
import { Component, OnInit, inject } from '@angular/core';
import {
FormGroup,
FormBuilder,
FormControl,
ReactiveFormsModule,
} from '@angular/forms';
import { RouterModule } from '@angular/router';
import { AlertController } from '@ionic/angular/standalone';
import { UnsavedChanges } from '../unsaved-changes-service/unsaved-changes';
import { Capacitor } from '@capacitor/core';
@Component({
selector: 'app-form1',
templateUrl: './form1.page.html',
styleUrls: ['./form1.page.scss'],
standalone: true,
imports: [
ReactiveFormsModule,
],
})
export class Form1implements OnInit, UnsavedChanges {
public newForm!: FormGroup;
constructor(
private formBuilder: FormBuilder,
public alertController: AlertController
) {}
async ngOnInit() {
this.newForm = this.formBuilder.group({
taskName: new FormControl(''),
taskDate: new FormControl(''),
});
}
isDirty(): boolean {
return this.newForm.dirty && this.hasNonEmptyValue();
}
handleUnsavedChanges(): () => void | Promise<void> {
return () => {
this.onSave();
};
}
hasNonEmptyValue(): boolean {
const formValues = this.newForm.value;
return Object.values(formValues).some(
(value) => value !== null && value !== undefined && value !== ''
);
}
async onSave() {
// handle the form save here.
}
}
Now you need to wrap the unsaved changes like a guard in the component to detect it.
In the app.route.ts we need to import the service:
import { Routes } from '@angular/router';
import { UnsavedChangesDetectService } from './unsaved-changes-service/unsaved-changes-detect.service';
export const routes: Routes = [
{
path: 'home',
loadComponent: () => import('./home/home.page').then((m) => m.HomePage),
},
{
path: '',
redirectTo: 'home',
pathMatch: 'full',
},
// Import the service with a canDeactivate.
{
path: 'form1',
canDeactivate: [UnsavedChangesDetectService],
loadComponent: () =>
import('./form1/form1.page').then((m) => m.Form1Page),
},
];
This will basically wrap the page/component with a guard and helps to detect the unsaved changes.
My LinkedIn : https://www.linkedin.com/in/mustafa-iram-bab3a8221/
Follow my github to get new features like this.
Subscribe to my newsletter
Read articles from Mustafa Iram directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mustafa Iram
Mustafa Iram
Hi, this is Iram. I am a full time Software Engineer.