Unsaved changes warning in Angular

Updated: 2023-11-13

Our goal is to alert the user if there are unsaved changes on the page, and she is navigating away from the page with the risk of losing the updated data.

We have 2 possible navigation issues.

  1. The user navigates away from the edited page, but she stays inside the Angular application.
  2. The user navigate away using the browser (e.g. a different website)

In the first case we have to handle the changes with Angular, in the first case is the browser who has to take care of the alert.

This means that you will have 2 different alerts for the 2 different use cases. The alert launched by the browser is not modifiable.

Case 1. The user try change page inside the same application with unsaved data

In Angular to execute a function when the user change the route you have to define a guard using canDeactivate function.

e.g. in your route definition:

{ 
    path: 'app/mypage/:id', 
    component: MyComponent, 
    canDeactivate: [canDeactivateUnsavedGuard] 
} 

your guard (can-deactivate-unsaved.guard.ts) will look like something like:

import ... 
 
export interface CanDeactivateUnsavedComponent { 
    isUnsaved(): Observable<boolean> | Promise<boolean> | boolean; 
} 
 
export const canDeactivateUnsavedGuard: CanDeactivateFn<CanDeactivateUnsavedComponent> = (component: CanDeactivateUnsavedComponent) { 
    if (component.isUnsaved()) { 
        const dialogRef = inject(MatDialog).open(YourUnsavedDialogComponent, {}) 
        return dialogRef.afterClosed().pipe( 
            map(result => { 
                return result as YourDialogResultAnwser === YourDialogResultAnswer.Confirm 
            })) 
        return true; 
    } 
} 

The code define an interface that has to be implemented by the component with editable data.

The guard calls the component to know if the data is modified but not saved. In this case, we show a Dialog asking the user if she is sure to quit the page.

An example implementation of the interface is:

[...] 
 
export class MyDataEditComponent implements CanDeactivateUnsavedComponent { 
    isUnsaved(): Observable<boolean> | boolean { 
        return myDataFunction.isUnsaved(); 
    } 
} 

When the user tries to leave the component, the guard will call isUnsaved().

Case 2. The user navigate away from the application using the browser

If the user change URL directly in the browser Angular doesn't have the right to stop the user. This is a security measure implemented by the browser.

What we can do in our application is to add to our component (e.g. MyDataEditComponent) some Javascript code that activates the default warning of the browser:

@HostListener('window: beforeunload', ['$event']) 
onBeforeUnload(event: BeforeUnloadEvent): void { 
    if (myDataFunction.isUnsaved()) { 
        event.preventDefault(); 
        event.stopPropagation(); 
        event.returnValue = 'any value :O'; 
} 
} 

This Javascript code will be triggered when the user is leaving our application (beforeunload) and it calls the previous validation method.

If the data is not saved, and we have to show a warning whe have to stop the 'standard' Javascript execution and return any valid value.

If you have a working PWA, iOS 17 (and, as reported by some developers, 16.4) could start to show the following error:

Safari can't open the page.

The error was:
"FetchEvent.respondWith received an error: TypeError: Internal Error".

Sometimes the PWA (Home page link on the iPhone) won't show any information simply showing a blank page.

This seems to generated by an error in Angular AND WebKit (aka the engine behind Safari).

The Angular team and the WebKit team have already implemented the fixes on their side. The Angular team backported the fix in some old versions (15).

I don't know when the WebKit team will distribute their own fix, with iOS 17 it seems not fixed yet.

If you have urgency to fix the issue you can navigate the discussion in GitHub to see if some workarounds works for you.

If you can wait, just update your Angular version to integrate the bugfix and wait that Apple will deliver it's own fix.


You could be interested in

Right click context menu with Angular

Right click custom menu inside dynamic lists with Angular Material
2020-05-31

Enums in Angular templates

How to use enum in the html template
2019-01-21
WebApp built by Marco using SpringBoot 3.2.4 and Java 21, in a Server in Switzerland