Search code examples
angularauthenticationroutesangular17auth-guard

when my auth-guard is called it renders the login page for a brief moment when reloading the page on a route that is with canActivate - Angular v17


The problem basically is that when I'm logged into the dashboard, every time I reload the browser page it renders the login component for an instant

TokenService

export class TokenService {
  isAuthentications: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
  constructor(@Inject(PLATFORM_ID) private platformId: Object) { 
    const token = this.getToken();
    if(token){
      this.updateToken(true)
    }
  }

  setToken(token: string){
    this.updateToken(true);
    localStorage.setItem('user', token)
  }
  updateToken(status: boolean){
    this.isAuthentications.next(status)
  }
  getToken(): string | null{
    if (typeof window !== 'undefined' && window.sessionStorage) {
      return localStorage.getItem('user');
    }

    return null
  }
}

AuthGuard

export const authGuard: CanActivateFn = (route, state) =\> {
const tokenService = inject(TokenService)
const router = inject(Router)
// tokenService.isAuthentications.subscribe({
//   next: (v) =\> {
//     if(!v){
//         router.navigate(['/login'])
//     }
//   }
// })
// return true;

return tokenService.isAuthentications.pipe(map( (user) =\> {
if(!user){
return router.createUrlTree(['/login']);
}else{
return true
}
}))
};
Routes
export const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: '', redirectTo: 'login', pathMatch: 'full'},
  {path: '' , component: LayoutComponent, children: [
    {path: 'dashboard', component: DashboardComponent, canActivate: [authGuard]  }
  ]}
];

gif that shows the problem

I've tried other other approaches on how to secure the route however, whenever my guard should redirect to 'login' it has this behavior


Solution

  • The issue is due to enabling SSR, since there is no window or localStorage on the server. The login page is getting generated on the server and when sent to frontend. Just before the UI is run again (hydration I guess!), we see a flash of the login page.

    To solve this issue, all you need to do is to make the login page show nothing when it's generated on the server, then it gives the impression that the app is loading. Alternatively you could also show the splash screen with a loader on the login page, when the mode is server, this will get rid of this bug.

    TS:

    ...
    export class LoginComponent {
      isUser = signal(false)
      isServer = false;
      
      constructor(private auth: AuthService, private router: Router, private tokenService: TokenService, 
        @Inject(PLATFORM_ID) platformId: Object){
          this.isServer = isPlatformServer(platformId);
        console.log('aqui')
        const userToken = this.tokenService.getToken()
        if(userToken){
          this.isUser.set(true)
        }
      }
      ...
    

    HTML:

    @if (!this.isUser() && !isServer) {
        <p>login works!</p>
    <button mat-raised-button (click)="login()"> LOGIN </button>
    }
    

    GitHub Repo


    Spinner example

    HTML

    @if(isServer) {
        <div style="height:100vh;width:100vw;background-color: rgb(194, 24, 91); display: flex; align-items: center; justify-content: center;">
            <div id="loading"></div>
        </div>
    } @else {
        @if (!this.isUser()) {
            <p>login works!</p>
        <button mat-raised-button (click)="login()"> LOGIN </button>
        }
    }
    

    CSS

    #loading {
      display: inline-block;
      width: 50px;
      height: 50px;
      border: 3px solid rgba(255,255,255,.3);
      border-radius: 50%;
      border-top-color: #fff;
      animation: spin 1s ease-in-out infinite;
      -webkit-animation: spin 1s ease-in-out infinite;
    }
    
    @keyframes spin {
      to { -webkit-transform: rotate(360deg); }
    }
    @-webkit-keyframes spin {
      to { -webkit-transform: rotate(360deg); }
    }
    

    TS:

    export class LoginComponent {
      isServer = false;
      isUser = signal(false)
    
      constructor(
        private auth: AuthService, 
        private router: Router, 
        private tokenService: TokenService,
        @Inject(PLATFORM_ID) platformId: Object){
          this.isServer = isPlatformServer(platformId);
        console.log('aqui')
        const userToken = this.tokenService.getToken()
        if(userToken){
          this.isUser.set(true)
        }
      }
    

    GitHub Repo