Angular: display a warning and prevent navigation when model is dirty
Tags: angular, typescript, guards, entrycomponents
Published: 2019-08-05 23:06:19

---
title: Angular: display a warning and prevent navigation when model is dirty
published: true
description: stop navigation
tags: Angular, Typescript, Guards, entryComponents
---

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 routed component needs to implement


* getRef() returns the component's container reference
* isDirty() contains our bespoke comparison logic

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

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

```typescript
@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`


```typescript
@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.


```typescript
export class ConfirmationDialogComponent{

    constructor(
    private confirmationDialogReferenceService: ConfirmationDialogReferenceService) { }

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

    public navigateAway() {
        this.confirmationDialogReferenceService.allow = true;
        this.confirmationDialogReferenceService.destroyComponentAndAllowNavigation();
    }
}
```
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

```typescript
@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
```typescript
    entryComponents: [
        ConfirmationDialogComponent
    ]
```

Apologies if there are any unused properties left, I tried to clean up. This whole example was lifted from a real world application