How to call canDeactivate in Angular when logout is clicked
Asked Answered
S

6

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.

Selfhypnosis answered 10/1, 2018 at 11:48 Comment(2)
Refer to the official Angular documentation - angular.io/api/router/CanDeactivateHairless
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.Illaudable
M
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();
  }
Mosenthal answered 26/4, 2019 at 8:47 Comment(0)
I
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

Idiographic answered 10/1, 2018 at 12:11 Comment(0)
D
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

Disjointed answered 10/1, 2018 at 12:7 Comment(4)
Thanks @trichetriche The logout function is on separate Component, how can I know, to call which canDeactivate() of the correct component?Selfhypnosis
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 ?Disjointed
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.Selfhypnosis
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.Disjointed
A
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

Aguilera answered 9/10, 2018 at 11:10 Comment(0)
D
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 https://mcmap.net/q/136686/-candeactivate-confirm-message
      confirm('You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.');
  }
  
}
Deerskin answered 22/9, 2021 at 22:6 Comment(0)
W
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

Whencesoever answered 24/7, 2020 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.