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.
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
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.ConfirmationDialogComponent
) into the view, through a service (ConfirmationDialogService
).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
ConfirmationDialogReferenceService
) and stay in the same view.Also from the IDirty Interface we have
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
@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