8

In my current logout call, I have something like this:

 getLogoutConfirmation(){
   // get confirmation in modal and perform logout
   // redirect to login page
 }

Issue is, when logout is performed and redirect to login is called, the canDeactivate guard is called on that route, but User has already been logged out.

So how do I call canDeactivate guard before the logout is performed and cancel logout action if user doesn't want to leave.

Thanks.

Roland Rácz
  • 2,879
  • 3
  • 18
  • 44
Gowtham Raj J
  • 937
  • 9
  • 26
  • Refer to the official Angular documentation - https://angular.io/api/router/CanDeactivate – Sandip Ghosh Jan 10 '18 at 11:54
  • You need a `canDeactivate` route guard on your current route. In the callback `getLogoutConfirmation`, you can show the modal and return a promise to confirm/cancel the logout. You can also have guards on the login route but they are not relevant in your question. – ConnorsFan Jan 10 '18 at 15:02

6 Answers6

3

Instead of logging out immediately, just navigate to your target (login?) location and add some extra paramter. Then add a "canActivate" guard to your target (login?) location, check for your extra parameter, and do the actual logout there. By this, the logout won't interfere with your canDeactivate guard and will happen (if canDeactivate says ok) at the correct time.

Change:

userLogout() {
  this.authenticationService.logout();
  this.router.navigate(['login']);
}

To:

userLogout() {
  this.router.navigate(['login'], { queryParams: { logout: true }});
}

And in the canActivate guard do something like this:

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): ... {
  if ('true' === next.queryParamMap.get('logout')){
    this.authenticationService.logout();
  }
Ronald Korze
  • 734
  • 5
  • 16
2

You should navigate to login page based on getLogoutConfirmation(). if you didn't navigate away then CanDeactivate wouldn't be activated.

Otherwise, Guard canDeactivate is get invoked when navigating away from the current view or route within your angular app. You could check and for getLogoutConfirmation within canDeactivate then decide to continue or cancel navigation. CanDeactivate guard can be used to handle the unsaved change, check permission and clean up the component. This doesn't handle the external navigations such as browser refreshes or closing . You need to use @HostListener('window:beforeunload') for these cases

nayakam
  • 4,149
  • 7
  • 42
  • 62
1

Simply call CanDeactivate in your logout call, and do you logic there :

canDeactivate() {
  if(confirm('Leave and logout ?') {
    this.loginService.logout();
    this.router.navigate('');
    return true;
  } else {
    return false;
  }
}

canDeactivate is just an implementation of an interface, you can call it as you would call any function

  • Thanks @trichetriche The logout function is on separate Component, how can I know, to call which canDeactivate() of the correct component? – Gowtham Raj J Jan 10 '18 at 14:12
  • The logout function should be in a service, as I showed in my answer. Otherwise, you need to call the `canDeactivate` of the component that will be destroyed. Do you know which one it is ? –  Jan 10 '18 at 14:13
  • I tried to navigate to `/login` on click of `logout()` so `CanDeactivate()` will be invoked and handle it in `canDeactivate`. And logout function is in separate service like you said, but the CanDeactivate is beind called recursively. – Gowtham Raj J Jan 10 '18 at 14:17
  • Let's say you have two components and a service : HomeComponent, LoginComponent, and LoginService. You should call logout from your service and redirect from HomeComponent (meaning that it's the canDeactivate of HomeComponent that will be called). If it's not your case, then post some code, because I can't understand your issue. –  Jan 10 '18 at 14:23
1

I had the same problem and I solved it by creating a component that handles the logging out functionality within the ngOnInit () and then redirect to the login page

 ngOnInit() {
    this.authenticationService.logout();
    this.router.navigate(['/login']);
  }

this will cause the route to be deactivate thus calling the canDeactivate guard and displaying the confirmation

Enas Osama
  • 211
  • 1
  • 7
1

I only have a few weeks of experience in angular. This is my workaround. I added the variable check for unsaved form in local storage.

<---------------------------------------------------------------------------------->

header.component.ts

logout(){
    this.formDetect= JSON.parse(localStorage.getItem('formDetect'));
    var res=true;
    if(this.formDetect.isUnsaved){
      res=confirm("are you sure you want to logout? You have an unsaved form");
    }
    if(res){
      //If you are using it somewhere else, make sure to set isUnsaved to false then set local storage.
//this.formDetect.isUnsaved=false;    
//localStorage.setItem("formDetect", JSON.stringify(this.formDetect));

      this.accountService.logout();
      this.router.navigateByUrl('/login');
    }
    
  }

<---------------------------------------------------------------------------------->

form.component.ts

export class FormComponent implements OnInit, SafeData {
  formDetect:FormDetect;
  @HostListener('window:beforeunload')
  isDataSaved():boolean{
    this.formDetect= JSON.parse(localStorage.getItem('formDetect'));
    return!this.formDetect.isUnsaved;
  }
  onFormChange(){
    this.formDetect.isUnsaved=true;
    localStorage.setItem("formDetect", JSON.stringify(this.formDetect));
  }
}

<---------------------------------------------------------------------------------->

export interface SafeData{
    isDataSaved():boolean;
}

<---------------------------------------------------------------------------------->

export class FormDetect{
    isUnsaved:boolean=false;
}

<---------------------------------------------------------------------------------->

Guard

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { SafeData } from '../_models/safeData';

@Injectable({
  providedIn:"root"
})
export class UnsavedCheckGuard implements CanDeactivate<SafeData> {

  canDeactivate(component: SafeData): boolean | Observable<boolean> {
    // if there are no pending changes, just allow deactivation; else confirm first
    var result= component.isDataSaved();
    return result ?
      true :
      // NOTE: this warning message will only be shown when navigating elsewhere within your angular app;
      // when navigating away from your angular app, the browser will show a generic warning message
      // see http://stackoverflow.com/a/42207299/7307355
      confirm('You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.');
  }
  
}
0

I have the same problem and solved this issue by checking the navigation response.

let say you have canDeactivate like this:

canActivate() {
  return windows.confirm("you have unsaved work, Are you still want to logout?")
  }

so with this function, you are asking if user want to do logout before saving the task? Now you can have this response inside your logout function. The function can be written like bellow:

userLogout() {
  
  this.router.navigate(['login']).then( (response) = > { 
      //response get the response from canDeactivate
     
      if (response) { 
       // if response is true, call the logout service
       this.authenticationService.logout();
      }  
  } 
}

Here in the function, if the user wants to leave the unsaved task, you will get true inside your userLogout function. Put this response inside an if statement. So your logout service will not be called automatically. By doing this you can solve the issue

Kazi
  • 1,461
  • 3
  • 19
  • 47