Angular effect() does not redirect to the home page after logout and re-login

Discussão em 'Angular' iniciado por Zed, Novembro 7, 2024.

  1. Zed

    Zed Guest

    I'm using ngrx signal store, and I have a login method that updates my state with isAuthenticated: true and token: token. It also stores the token in localStorage. I have a withComputed((store) => ({ isLogged: computed(() => store.isAuthenticated()) }) setup.

    When I log in, my redirectToLoginEffect works correctly and redirects as expected. However, when I call my logout: async () => { } method, which sets isAuthenticated: false and uses router.navigate(['login']) to redirect to the login page, there's an issue when logging back in. Although the token is added to localStorage, the redirectToLoginEffect does not redirect me to the home page as expected. I have to refresh the login page and re-enter my credentials for it to work.

    Ngrx Signal Store :

    import { computed, inject } from '@angular/core';
    import { Router } from '@angular/router';
    import {
    patchState,
    signalStore,
    withComputed,
    withHooks,
    withMethods,
    withState,
    } from '@ngrx/signals';
    import { rxMethod } from '@ngrx/signals/rxjs-interop';
    import { pipe, tap, concatMap, map } from 'rxjs';
    import { tapResponse } from '@ngrx/operators';
    import {
    LocalStorageAuthenticationInfrastructure,
    WithToken,
    } from '../services/localstorage.authentication.infrastructure';
    import { AuthenticationToken } from '../models/authentication-user';
    import { AuthenticationInfrastructure } from '../services/authentication.infrastructure';
    import { Storage } from '@ionic/storage-angular';

    export interface AuthenticationState {
    token: AuthenticationToken | undefined | null;
    isLoading: boolean;
    isAuthenticated: boolean;
    }

    export type AuthenticateType = {
    login: string;
    password: string;
    };

    const initialValue: AuthenticationState = {
    isAuthenticated: false,
    token: undefined,
    isLoading: false,
    };

    export const AuthenticationStore = signalStore(
    { providedIn: 'root' },
    withState(initialValue),
    withComputed((store) => ({
    isLogged: computed(() => store.isAuthenticated()),
    isNotLogged: computed(() => !store.token() || !store.isAuthenticated()),
    })),
    withMethods(
    (
    store,
    infra = inject(AuthenticationInfrastructure),
    localInfra = inject(LocalStorageAuthenticationInfrastructure),
    router = inject(Router)
    ) => ({
    localLogin(token: WithToken): void {
    if (token.access_token) {
    patchState(store, {
    isAuthenticated: true,
    token: {
    access_token: token.access_token,
    expires_in: 0,
    refresh_expires_in: 0,
    refresh_token: '',
    },
    });
    }
    },
    logIn: rxMethod<AuthenticateType>(
    pipe(
    tap(() => patchState(store, { isLoading: true })),
    concatMap((input) => {
    return infra.login(input.login, input.password).pipe(
    map((tokenResponse) => {
    return {
    access_token: tokenResponse.access_token,
    expires_in: tokenResponse.expires_in,
    refresh_expires_in: tokenResponse.refresh_expires_in,
    refresh_token: tokenResponse.refresh_token,
    } as AuthenticationToken;
    }),
    tap((token) =>
    localInfra.startSession({
    access_token: token.access_token,
    refresh_token: token.refresh_token,
    })
    ),
    tapResponse({
    next: (token) => {
    patchState(store, {
    isLoading: false,
    isAuthenticated: true,
    token: token,
    });
    },
    error: () => {
    patchState(store, { isLoading: false });
    },
    })
    );
    })
    )
    ),
    logout: async () => {
    patchState(store, {
    token: undefined,
    isLoading: false,
    isAuthenticated: false,
    });
    console.log("index")
    await localInfra.endSession();
    router.navigate(['login']);
    },
    })
    ),
    withHooks({
    async onInit(
    store,
    localInfra = inject(LocalStorageAuthenticationInfrastructure),
    storage = inject(Storage)
    ) {
    const test = await localInfra.getSession();
    store.localLogin(test);

    // localInfra.getSession().then((token : WithToken ) => {
    // if (token) {
    // console.log(token)
    // store.localLogin(token);
    // }
    // });
    },
    })
    );


    Effect Method :

    export class AuthenticationApplication {
    private readonly store = inject(AuthenticationStore);
    private readonly router = inject(Router);

    private redirectToLoginEffect = effect(() => {
    if (this.store.isLogged()) {
    this.router.navigate(['tabs']);
    }
    });
    }


    LocalStorageService :

    export class LocalStorageAuthenticationInfrastructure {
    private storageInitialised = false;

    private _storage = inject(Storage);

    async startSession(user: WithToken) {
    if (!this.storageInitialised) await this._storage.create();
    if (user.access_token && user.refresh_token) {
    this._storage?.set('access_token', user.access_token);
    this._storage?.set('refresh_token', user.refresh_token);
    }
    }

    async getSession(): Promise<WithToken> {
    if (!this.storageInitialised) await this._storage.create();
    return {
    access_token: await this._storage?.get('access_token'),
    refresh_token: await this._storage?.get('refresh_token'),
    };
    }

    async endSession(): Promise<void> {
    console.log("clear")
    await this._storage.clear();
    }
    }


    JWT interceptor that triggers logout when refresh token expires :

    export function jwtInterceptor(req: HttpRequest<unknown>,next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
    const localStorage = inject(LocalStorageAuthenticationInfrastructure);
    const authInfra = inject(AuthenticationInfrastructure);
    const authApp = inject(AuthenticationApplication);

    return from(localStorage.getSession()).pipe(
    switchMap((token: WithToken) => {
    if (!token.access_token) {
    return next(req);
    }

    const headers = new HttpHeaders({
    Authorization: `Bearer ${token.access_token}`,
    });

    const newReq = req.clone({ headers });

    return next(newReq).pipe(
    catchError((error) =>{
    if(error instanceof HttpErrorResponse){
    switch(error.status){
    case 0:
    console.log("rst")
    return authInfra.refreshToken(token.refresh_token as string).pipe(
    tap(() => console.log("refreshToken appelé avec le refresh_token :", token.refresh_token)), // Vérifier que la méthode est appelée

    switchMap((response) =>{
    localStorage.startSession({
    access_token: response.access_token,
    refresh_token: response.refresh_token,
    })

    const newReq = req.clone({
    setHeaders:{
    'Authorization' : `Bearer ${response.access_token}`
    }
    });

    return next(newReq);
    }),
    catchError((error) =>{
    console.log("je suis passé par logout")
    authApp.logout()
    return throwError(() => error);
    })
    )
    }
    }
    return throwError(() => error);
    })
    );
    })
    );
    }


    My effect doesn't trigger when my state changes. Why do I need to refresh the login page for it to work?

    Continue reading...

Compartilhe esta Página