Reputation: 1644
google.com
is the homepage. I need two different routes for the homepage. The first route works while the user is not logged in (Auth) and the second route works while the user is logged in (Dashboard).
The hard part is when both auth and dashboard pages work on a single homepage, not different URLs.
Note: Both modules have multiple components. Auth has (login - signup - reset) components and Dashboard has (index - users - posts) components.
const routes: Routes = [
{
path: 'index',
loadChildren: './modules/index/index.module#IndexModule'
},
{
path: 'error',
loadChildren: './modules/error/error.module#ErrorModule'
},
// Fallback when no prior routes is matched
{ path: '**', redirectTo: 'not-found', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: false })],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule { }
const routes: Routes = [
{
path: '',
children: [
{
path: '',
loadChildren: './modules/home/home.module#HomeModule',
canLoad: [AuthGuard],
},
{
path: 'auth',
loadChildren: './modules/auth/auth.module#AuthModule',
canLoad: [NoAuthGuard]
},
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class IndexRoutingModule { }
const routes: Routes = [
{
path: '',
component: HomeLayoutComponent,
pathMatch: 'full',
children: [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
component: DashboardComponent,
data: { title: 'Dashboard' }
},
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }
const routes: Routes = [
{
path: '',
component: AuthLayoutComponent,
pathMatch: 'full',
children: [
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
},
{
path: 'login',
component: LoginComponent,
data: { title: 'Sign In To Admin' }
},
]
}
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [
RouterModule
]
})
export class AuthRoutingModule { }
The problem is only with routing, not guards and not anything.
Upvotes: 2
Views: 165
Reputation: 367
There's a detailed answer on another question about matching different routes with the same URL depending on an asynchronous condition.
In short, in Angular 14.1+ use CanMatch
guards. In fact, CanLoad
is planned to be deprecated in Angular 15.1 in favor of CanMatch
.
There is an issue with using CanLoad
for this purpose in the first place: you would also need CanActivate
, since CanLoad
only runs once (if the child routes were loaded it will not run again).
Now with CanActivate
added, it does not run before the routes' matcher
s, so the previous answer for this question is incorrect. Angular first determines the route (the entire route path from the parent to the innermost child), and only then runs CanActivate
guards.
For the approach from that answer to work (using matcher
s with a variable that gets updated in the CanActivate
guard), you could redirect in canActivate
if the new value is different, to trigger another route matching with the updated auth state. You'd also need to make sure both of those routes match the other's sub routes (or add a wildcard child route), or there will be cases where the current auth state will cause no child route to be matched, and canActivate
will not run.
One last thing is replacing CanActivate
with CanActivateChild
.
Upvotes: 0
Reputation: 54811
The first route works while the user is not logged in (Auth) and the second route works while the user is logged in (Dashboard).
It's tricky because the user's state is difficult to access from the router configuration.
I recommend you use a UrlMatcher to control which route configuration is used and when it is used.
https://angular.io/api/router/UrlMatcher
The tricky part is accessing the current user's logged in state, and I assume this state is only known by a service. Something like UsersService.isLogged()
that returns an observable of true/false, because maybe it needs to contact the server to restore a previous session.
So what I would do is used an activator to read this state into a global variable, and then use that variable from the UrlMatcher. The activator doesn't do any logical control of the route, because it will always yield a true to activate the route. We just want to gain access to a service and perform an async operation.
If you define an empty parent route that uses an activator, then I assume that Angular will resolve the activator before processing child routes.
let globalUserState = false;
class UserActivator implements CanActivate {
public constructor(private users: UserService) {
}
public canActivate(): Observable<boolean> {
return this.users.isLoggedIn().pipe(
tap(value => globalUserState = value),
mapTo(true)
);
}
}
const routes: Routes = [
{
path: '',
canActivate: [UserActivator],
children: [
{
// you can define more than one child using different matching rules
matcher: (url: UrlSegment[]) => {
return url.length === 1 && url[0].path === '/' && globalUserState ? ({consumed: url}) : null;
},
children: [
{
// lazy modules must be loaded as children
path: '',
loadChildren: '../lazy/home/home.module#HomeModule'
}
]
},
]
}
];
Upvotes: 1