Story:
Greetings to all! As a newcomer to using the NgRx library, I have encountered an issue in my Angular project where I am utilizing a router guard to prevent unauthorized users from accessing certain parts of the website.
My approach involves calling a REST API to verify the content of a token stored in local storage. Upon receiving a "success" result from the API, I perform "store.dispatch(LoginSuccess)" before returning true to update the login authentication information in the Store.
Problem:
The problem arises when executing the dispatch action. The "LoginSuccess" action seems to trigger an infinite loop even after setting { dispatch: false } in @Effect(). Despite researching and attempting various solutions found through Google, this issue continues to persist within my project. Any assistance or insights from the Stack Overflow community would be greatly appreciated. Thank you in advance.
Snippet of code:
auth.guard.ts
.....
....
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const authData: {email: string, token: string} = JSON.parse(
String(localStorage.getItem('User')));
const authToken = authData && authData.token;
// loginServ is a REST API for verify token
return this.loginServ.VerifyToken(authToken).pipe(
concatMap((res: AuthResult) => {
if (res.result === 'success') {
this.store.dispatch(LoginSuccess({
isAuthenticated: true,
email: authData && authData.email,
token: authToken,
result: 'success',
message: 'success'
})); // <-- execute action infinite.
return of(true);
} else {
this.store.dispatch(LoginFailed({
isAuthenticated: false,
email: authData && authData.email,
token: authToken,
result: 'failed',
message: 'token illegal'
}));
localStorage.removeItem('User');
this.router.navigate(['']);
return of(false);
}
})
);
...
...
action.ts:
...
export const LoginSuccess = createAction(
'[auth] Login Success',
props<{
isAuthenticated: boolean,
email: string,
token: string,
result: string,
message: string,
}>()
);
....
...
reducer.ts
....
....
export const loginReducer = createReducer(
defaultState,
on(Login),
on(LoginSuccess, (state, userInfo) => {
console.log(state); // <-- for debug.
console.log(userInfo); // <-- for debug.
return {
...state,
...userInfo
};
}),
on(LoginFailed, (state, result) => {
return {
...state,
...result
};
}),
....
...
effect.ts
....
...
loginSuccess = createEffect(() => this.actions$
.pipe(
ofType(LoginSuccess),
map((user: any) => {
console.log(user);
const beStoreInfo = {
email: user.email,
token: user.token,
};
localStorage.setItem(
'User',
JSON.stringify(beStoreInfo));
return {
type: '[auth] Login Success',
isAuthenticated: true,
email: user.email,
token: user.token,
result: user.result,
message: user.message
};
}),
), {dispatch: false});
....
...
service_overview-routing.module.ts
....
// Using lazy loading module
const routes: Routes = [
{
path: '',
canActivate: [AuthGuard],
component: ServiceOverviewComponent},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ServiceOverviewRoutingModule { }
....
app-routing.module.ts
Implementing lazy loading modules.
....
...
const routes: Routes = [
{ path: 'login', loadChildren: () => import('./login/login.module')
.then(m => m.LoginModule)},
{ path: 'register', loadChildren: () => import('./register/register.module')
.then(m => m.RegisterModule)},
{ path: 'service_overview', loadChildren: () => import('./service-overview/service-overview.module')
.then(m => m.ServiceOverviewModule)},
{ path: '', redirectTo: 'login', pathMatch: 'full' }
];
....
Environment:
Angular CLI: 8.0.3
NgRx Store/Effect: 8.0.1
OS: Ubuntu 18.04
..Thanks