Angular: display a warning and prevent navigation when model is dirty

Angular is one of my favourite frameworks. I know there are a lot haters out there, as well as many lovers, for good reasons. When it comes to enterprise applications, its where it shines.

Today I decided to write on how you can create a component that gets dynamically inserted into the dom and prevent navigation, if and when your view's model is dirty.

This technique is exceptionally handy, especially when you build an application with a lot of forms.

What we try to achieve is basically, if the form has changes (either through creating or editing a form) and the user accidently tries to navigate away, give him a choice.

  • to go back and save his changes
  • or accept that his changes will be lost and navigate away

This is the interface that our form component needs to implement

interface IDirty {
    isDirty(): boolean;
    getRef(): ViewContainerRef
}

This is the guard service that implements CanDeactivate. Which basically blocks navigation or not by returning true/false.

@Injectable()
export class ModelDirtyGuardService implements CanDeactivate<IDirty> {
    constructor(
        private confirmationDialogService: ConfirmationDialogService,
        private confirmationDialogReferenceService: ConfirmationDialogReferenceService
    ) { }
    public canDeactivate(
        component: IDirty,
        currentRoute: ActivatedRouteSnapshot,
        currentState: RouterStateSnapshot,
        nextState: RouterStateSnapshot
    ): Observable<boolean> | Promise<boolean> | boolean {
        let canLeave: boolean = component.isDirty();
        if (canLeave === false) {
            canLeave = this.confirmationDialogService.loadComponent(
              component.getRef(),
              nextState.url
            );
            this.confirmationDialogReferenceService.allow = false;
        } else {
            this.confirmationDialogReferenceService.allow = false;
        }
    return canLeave;
}

}

ConfirmationDialogService is the service that is responsible for constructing and inserting our component to the dom.

ConfirmationDialogReferenceService holds a global state of the component we want to insert.

what is happening is simple

  • we check if the model is dirty by using the isDirty function that our component needs to implement. One of the things that we are not covering here, is how to check if the form/model is dirty. Depends on your application logic.
  • if the model is dirty that means that we need to insert our component (ConfirmationDialogComponent) into the view, through a service (ConfirmationDialogService).
  • set the state allow through the ConfirmationDialogReferenceService
@Injectable()
export class ConfirmationDialogService {
answer: boolean;
componentRef: ComponentRef<ConfirmationDialogComponent>;

constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private confirmationDialogReferenceService: ConfirmationDialogReferenceService
) { }

loadComponent(viewContainerRef: ViewContainerRef, nextState) {
    this.confirmationDialogReferenceService.routerState = nextState;
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(ConfirmationDialogComponent);
    this.componentRef = viewContainerRef.createComponent(componentFactory);
    this.confirmationDialogReferenceService.componentRef = this.componentRef;
    return this.confirmationDialogReferenceService.allow;
}

}

Next we create our component which implements IDirty and it has two functions

  • closeDialog(), we decide that we need not to navigate away so we need to unload the dynamic component (this is done by destroying it through ConfirmationDialogReferenceService) and stay in the same view.
  • navigateAway() we accept that we agree to navigate away, losing any changes.

Also from the IDirty Interface we have

  • getRef() which returns the component’s container reference
  • isDirty() which contains our bespoke comparison logic
export class ConfirmationDialogComponent implements IDirty{

constructor(
private confirmationDialogReferenceService: ConfirmationDialogReferenceService,
private elementRef: ElementRef,
private viewContainerRef: ViewContainerRef) { }

public closeDialog() {
    this.confirmationDialogReferenceService.unloadComponent();
}

public navigateAway() {
    this.confirmationDialogReferenceService.allow = true;
    this.confirmationDialogReferenceService.destroyComponentAndAllowNavigation();
}

public getRef() {
  return this.viewContainerRef;
}

public isDirty() {
  // bespoke logic
}

}

Finally we have ConfirmationDialogReferenceService, which keeps the current state of our dynamic ConfirmationDialogComponent component.

The important bits is

  • routerState, which we set the route we need to navigate to
  • unloadComponent, which destroys our component (we stay in the same view)
  • destroyComponentAndAllowNavigation, which destroys our component and let us navigate away
@Injectable()
export class ConfirmationDialogReferenceService {
private _componentRef: any;
private _routerState: string;
private _allow: boolean;

constructor(
    private router: Router
) {

}

set componentRef(ref) {
    this._componentRef = ref;
}

get componentRef() {
    return this._componentRef;
}

set allow(allow) {
    this._allow = allow;
}

get allow() {
    return this._allow;
}

set routerState(state) {
    this._routerState = state;
}

get routerState() {
    return this._routerState;
}

public unloadComponent() {
    this.componentRef.destroy();
}

public destroyComponentAndAllowNavigation() {
    this.componentRef.destroy();
    this.router.navigate([this.routerState]);
}

}

Last but not least in order to use the createComponent function, our component needs to be an entryComponents in our Module

    entryComponents: [
ConfirmationDialogComponent
]

Thanks For Visiting, Keep Visiting.

☞ Angular 8 (formerly Angular 2) - The Complete Guide

☞ Complete Angular 8 from Zero to Hero | Get Hired

☞ Learn and Understand AngularJS

☞ The Complete Angular Course: Beginner to Advanced

☞ Angular Crash Course for Busy Developers

☞ Angular Essentials (Angular 2+ with TypeScript)

☞ Angular (Full App) with Angular Material, Angularfire & NgRx

☞ Angular & NodeJS - The MEAN Stack Guide


This post was originally published here

#angular #angular-js #typescript #web-development

Angular: display a warning and prevent navigation when model is dirty
33.45 GEEK