Search code examples
angulartypescriptauthenticationlazy-loadinginfinite-loop

Why is canLoad function resulting in infinite loop during reroute?


I'm using Angular's canLoad function in my AuthGuard to authenticate a lazy loaded module on the root of my app.

If the user is not authenticated then module is not loaded and user is navigated to the login page. This works perfectly fine with canActivate but results in an infinite loop during the start of my application for canLoad.

I'm not sure where or how the loop is produced since my auth component is part of a different module then the one I'm trying to load and the redirect is not to the root. I've found similar problems/articles but their cases were more blatant.

AppRoutingModule

const routes: Routes = [
{
    path: '',
    component: AdminLayoutComponent,
    canLoad: [AuthGuard],
    loadChildren: () => import('./_layouts/admin-layout/admin-layout.module').then(mod => mod.AdminLayoutModule)
},
{
    path: 'login',
    component: AuthComponent
},
{
    path: '404',
    component: PageNotFoundComponent,
}
];

AuthGaurdService

    @Injectable({
    providedIn: 'root',
})
export class AuthGuard implements CanActivate, CanLoad {
    constructor(private router: Router, private userService: UserService) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        return this.userService.isAuthenticated.pipe(take(1)).map(auth => {
            if (auth == true) {
                return true;
            }
            console.warn('User not authenticated. ACCESS DENIED.');
            // navigate to login page
            this.router.navigate(['/login']);
            // you can save redirect url so after authing we can move them back to the page they requested
            return false;
        });
    }

    canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean {
        return this.userService.isAuthenticated.pipe(take(1)).map(auth => {
            if (auth == true) {
                return true;
            }
            console.warn('User not authenticated. ACCESS DENIED.');
            // navigate to login page
            this.router.navigate([route.path + '/login']);
            // you can save redirect url so after authing we can move them back to the page they requested
            return false;
        });
    }
  }

AuthComponent SubmitMethod

    submitForm(): void {
    this.isSubmitting = true;
    this.errors = {errors: {}};

    const credentials = (this.authType == 'login' ? this.loginForm.value : this.registerForm.value);
    this.userService
        .attemptAuth(this.authType, credentials).subscribe(
        data => {
            console.log('Redirecting to profile');
            // Make profile default
            this.router.navigateByUrl('/profile');
        },
        err => {
            console.log('ERROR: ', err);
            this.errors = err;
            this.isSubmitting = false;
            // Redirect back to register/login
        }
    );

Solution

  • The order of the routes in the configuration matters and this is by design. The router uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. In the configuration above, routes with a static path are listed first, followed by an empty path route, that matches the default route. The wildcard route comes last because it matches every URL and should be selected only if no other routes are matched first. (quote from https://angular.io/guide/router)