Milestone 5: Route guards
At the moment, any user can navigate anywhere in the application anytime. That's not always the right thing to do.
- Perhaps the user is not authorized to navigate to the target component.
- Maybe the user must login (authenticate) first.
- Maybe you should fetch some data before you display the target component.
- You might want to save pending changes before leaving a component.
- You might ask the user if it's OK to discard pending changes rather than save them.
You can add guards to the route configuration to handle these scenarios.
A guard's return value controls the router's behavior:
- If it returns
true
, the navigation process continues. - If it returns
false
, the navigation process stops and the user stays put.
The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation.
The guard might return its boolean answer synchronously. But in many cases, the guard can't produce an answer synchronously. The guard could ask the user a question, save changes to the server, or fetch fresh data. These are all asynchronous operations.
Accordingly, a routing guard can return an
Observable
or a Promise
and the router will wait for the observable to resolve to true
or false
.
The router supports multiple guard interfaces:
CanActivate
to mediate navigation to a route.CanActivateChild
to mediate navigation to a child route.CanDeactivate
to mediate navigation away from the current route.Resolve
to perform route data retrieval before route activation.CanLoad
to mediate navigation to a feature module loaded asynchronously.
You can have multiple guards at every level of a routing hierarchy. The router checks the
CanDeactivate
and CanActivateChild
guards first, from the deepest child route to the top. Then it checks the CanActivate
guards from the top down to the deepest child route. If the feature module is loaded asynchronously, the CanLoad
guard is checked before the module is loaded. If any guard returns false, pending guards that have not completed will be canceled, and the entire navigation is canceled.
There are several examples over the next few sections.
CanActivate: requiring authentication
Applications often restrict access to a feature area based on who the user is. You could permit access only to authenticated users or to users with a specific role. You might block or limit access until the user's account is activated.
The
CanActivate
guard is the tool to manage these navigation business rules.Add an admin feature module
In this next section, you'll extend the crisis center with some new administrative features. Those features aren't defined yet. But you can start by adding a new feature module named
AdminModule
.
Create an
admin
folder with a feature module file, a routing configuration file, and supporting components.
The admin feature file structure looks like this:
src/app/admin
The admin feature module contains the
AdminComponent
used for routing within the feature module, a dashboard route and two unfinished components to manage crises and heroes.
Since the admin dashboard
RouterLink
is an empty path route in the AdminComponent
, it is considered a match to any route within the admin feature area. You only want the Dashboard
link to be active when the user visits that route. Adding an additional binding to the Dashboard
routerLink,[routerLinkActiveOptions]="{ exact: true }"
, marks the ./
link as active when the user navigates to the /admin
URL and not when navigating to any of the child routes.
The initial admin routing configuration:
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
Component-less route: grouping routes without a component
Looking at the child route under the
AdminComponent
, there is a path
and a children
property but it's not using a component
. You haven't made a mistake in the configuration. You've defined a component-lessroute.
The goal is to group the
Crisis Center
management routes under the admin
path. You don't need a component to do it. A component-less route makes it easier to guard child routes.
Next, import the
AdminModule
into app.module.ts
and add it to the imports
array to register the admin routes.import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './not-found.component';
import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './heroes/heroes.module';
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
import { AdminModule } from './admin/admin.module';
import { DialogService } from './dialog.service';
@NgModule({
imports: [
CommonModule,
FormsModule,
HeroesModule,
CrisisCenterModule,
AdminModule,
AppRoutingModule
],
declarations: [
AppComponent,
PageNotFoundComponent
],
providers: [
DialogService
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Add an "Admin" link to the
AppComponent
shell so that users can get to this feature.template: `
Angular
Router
a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> <a routerLink="/heroes" routerLinkActive="active">Heroes</a> <a routerLink="/admin" routerLinkActive="active">Admin</a> <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a> <router-outlet></router-outlet> <router-outlet name="popup"></router-outlet> `
Guard the admin feature
Currently every route within the Crisis Center is open to everyone. The new admin feature should be accessible only to authenticated users.
You could hide the link until the user logs in. But that's tricky and difficult to maintain.
Instead you'll write a
canActivate()
guard method to redirect anonymous users to the login page when they try to enter the admin area.
This is a general purpose guard—you can imagine other features that require authenticated users—so you create an
auth-guard.service.ts
in the application root folder.
At the moment you're interested in seeing how guards work so the first version does nothing useful. It simply logs to console and
returns
true immediately, allowing navigation to proceed:import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate() {
console.log('AuthGuard#canActivate called');
return true;
}
}
Next, open
admin-routing.module.ts
, import the AuthGuard
class, and update the admin route with a canActivate
guard property that references it:import { AuthGuard } from '../auth-guard.service';
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
],
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
The admin feature is now protected by the guard, albeit protected poorly.
Teach AuthGuard to authenticate
Make the
AuthGuard
at least pretend to authenticate.
The
AuthGuard
should call an application service that can login a user and retain information about the current user. Here's a demo AuthService
:import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap, delay } from 'rxjs/operators';
@Injectable()
export class AuthService {
isLoggedIn = false;
// store the URL so we can redirect after logging in
redirectUrl: string;
login(): Observable {
return of(true).pipe(
delay(1000),
tap(val => this.isLoggedIn = true)
);
}
logout(): void {
this.isLoggedIn = false;
}
}
Although it doesn't actually log in, it has what you need for this discussion. It has an
isLoggedIn
flag to tell you whether the user is authenticated. Its login
method simulates an API call to an external service by returning an observable that resolves successfully after a short pause. The redirectUrl
property will store the attempted URL so you can navigate to it after authenticating.
Revise the
AuthGuard
to call it.import { Injectable } from '@angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page with extras
this.router.navigate(['/login']);
return false;
}
}
Notice that you inject the
AuthService
and the Router
in the constructor. You haven't provided the AuthService
yet but it's good to know that you can inject helpful services into routing guards.
This guard returns a synchronous boolean result. If the user is logged in, it returns true and the navigation continues.
The
ActivatedRouteSnapshot
contains the future route that will be activated and the RouterStateSnapshot
contains the future RouterState
of the application, should you pass through the guard check.
If the user is not logged in, you store the attempted URL the user came from using the
RouterStateSnapshot.url
and tell the router to navigate to a login page—a page you haven't created yet. This secondary navigation automatically cancels the current navigation; checkLogin()
returns false
just to be clear about that.Add the LoginComponent
You need a
LoginComponent
for the user to log in to the app. After logging in, you'll redirect to the stored URL if available, or use the default URL. There is nothing new about this component or the way you wire it into the router configuration.
Register a
/login
route in the login-routing.module.ts
and add the necessary providers to the providers
array. In app.module.ts
, import the LoginComponent
and add it to the AppModule
declarations
. Import and add the LoginRoutingModule
to the AppModule
imports as well.
Guards and the service providers they require must be provided at the module-level. This allows the Router access to retrieve these services from the
Injector
during the navigation process. The same rule applies for feature modules loaded asynchronously.CanActivateChild: guarding child routes
You can also protect child routes with the
CanActivateChild
guard. The CanActivateChild
guard is similar to the CanActivate
guard. The key difference is that it runs before any child route is activated.
You protected the admin feature module from unauthorized access. You should also protect child routes within the feature module.
Extend the
AuthGuard
to protect when navigating between the admin
routes. Open auth-guard.service.ts
and add the CanActivateChild
interface to the imported tokens from the router package.
Next, implement the
canActivateChild()
method which takes the same arguments as the canActivate()
method: an ActivatedRouteSnapshot
and RouterStateSnapshot
. The canActivateChild()
method can return an Observable
or Promise
for async checks and a boolean
for sync checks. This one returns a boolean
:import { Injectable } from '@angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanActivateChild
} from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this.canActivate(route, state);
}
/* . . . */
}
Add the same
AuthGuard
to the component-less
admin route to protect all other child routes at one time instead of adding the AuthGuard
to each route individually.const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
canActivateChild: [AuthGuard],
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
CanDeactivate: handling unsaved changes
Back in the "Heroes" workflow, the app accepts every change to a hero immediately without hesitation or validation.
In the real world, you might have to accumulate the users changes. You might have to validate across fields. You might have to validate on the server. You might have to hold changes in a pending state until the user confirms them as a group or cancels and reverts all changes.
What do you do about unapproved, unsaved changes when the user navigates away? You can't just leave and risk losing the user's changes; that would be a terrible experience.
It's better to pause and let the user decide what to do. If the user cancels, you'll stay put and allow more changes. If the user approves, the app can save.
You still might delay navigation until the save succeeds. If you let the user move to the next screen immediately and the save were to fail (perhaps the data are ruled invalid), you would lose the context of the error.
You can't block while waiting for the server—that's not possible in a browser. You need to stop the navigation while you wait, asynchronously, for the server to return with its answer.
You need the
CanDeactivate
guard.Cancel and save
The sample application doesn't talk to a server. Fortunately, you have another way to demonstrate an asynchronous router hook.
Users update crisis information in the
CrisisDetailComponent
. Unlike the HeroDetailComponent
, the user changes do not update the crisis entity immediately. Instead, the app updates the entity when the user presses the Save button and discards the changes when the user presses the Cancel button.
Both buttons navigate back to the crisis list after save or cancel.
cancel() {
this.gotoCrises();
}
save() {
this.crisis.name = this.editName;
this.gotoCrises();
}
What if the user tries to navigate away without saving or canceling? The user could push the browser back button or click the heroes link. Both actions trigger a navigation. Should the app save or cancel automatically?
This demo does neither. Instead, it asks the user to make that choice explicitly in a confirmation dialog box that waits asynchronously for the user's answer.
You could wait for the user's answer with synchronous, blocking code. The app will be more responsive—and can do other work—by waiting for the user's answer asynchronously. Waiting for the user asynchronously is like waiting for the server asynchronously.
The
DialogService
, provided in the AppModule
for app-wide use, does the asking.
It returns an
Observable
that resolves when the user eventually decides what to do: either to discard changes and navigate away (true
) or to preserve the pending changes and stay in the crisis editor (false
).
Create a guard that checks for the presence of a
canDeactivate()
method in a component—any component. The CrisisDetailComponent
will have this method. But the guard doesn't have to know that. The guard shouldn't know the details of any component's deactivation method. It need only detect that the component has a canDeactivate()
method and call it. This approach makes the guard reusable.
- import { Injectable } from '@angular/core';
- import { CanDeactivate } from '@angular/router';
- import { Observable } from 'rxjs';
-
- export interface CanComponentDeactivate {
- canDeactivate: () => Observable
| Promise | boolean;
- }
-
- @Injectable()
- export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
- canDeactivate(component: CanComponentDeactivate) {
- return component.canDeactivate ? component.canDeactivate() : true;
- }
- }
Alternatively, you could make a component-specific
CanDeactivate
guard for the CrisisDetailComponent
. The canDeactivate()
method provides you with the current instance of the component
, the current ActivatedRoute
, and RouterStateSnapshot
in case you needed to access some external information. This would be useful if you only wanted to use this guard for this component and needed to get the component's properties or confirm whether the router should allow navigation away from it.import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { CrisisDetailComponent } from './crisis-center/crisis-detail.component';
@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent> {
canDeactivate(
component: CrisisDetailComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable | boolean {
// Get the Crisis Center ID
console.log(route.paramMap.get('id'));
// Get the current URL
console.log(state.url);
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
if (!component.crisis || component.crisis.name === component.editName) {
return true;
}
// Otherwise ask the user with the dialog service and return its
// observable which resolves to true or false when the user decides
return component.dialogService.confirm('Discard changes?');
}
}
Looking back at the
CrisisDetailComponent
, it implements the confirmation workflow for unsaved changes.canDeactivate(): Observable | boolean {
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
if (!this.crisis || this.crisis.name === this.editName) {
return true;
}
// Otherwise ask the user with the dialog service and return its
// observable which resolves to true or false when the user decides
return this.dialogService.confirm('Discard changes?');
}
Notice that the
canDeactivate()
method can return synchronously; it returns true
immediately if there is no crisis or there are no pending changes. But it can also return a Promise
or an Observable
and the router will wait for that to resolve to truthy (navigate) or falsy (stay put).
Add the
Guard
to the crisis detail route in crisis-center-routing.module.ts
using the canDeactivate
array property.import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisCenterHomeComponent } from './crisis-center-home.component';
import { CrisisListComponent } from './crisis-list.component';
import { CrisisCenterComponent } from './crisis-center.component';
import { CrisisDetailComponent } from './crisis-detail.component';
import { CanDeactivateGuard } from '../can-deactivate-guard.service';
const crisisCenterRoutes: Routes = [
{
path: '',
redirectTo: '/crisis-center',
pathMatch: 'full'
},
{
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: '',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard]
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(crisisCenterRoutes)
],
exports: [
RouterModule
]
})
export class CrisisCenterRoutingModule { }
Add the
Guard
to the main AppRoutingModule
providers
array so the Router
can inject it during the navigation process.
- import { NgModule } from '@angular/core';
- import { RouterModule, Routes } from '@angular/router';
-
- import { ComposeMessageComponent } from './compose-message.component';
- import { CanDeactivateGuard } from './can-deactivate-guard.service';
- import { PageNotFoundComponent } from './not-found.component';
-
- const appRoutes: Routes = [
- {
- path: 'compose',
- component: ComposeMessageComponent,
- outlet: 'popup'
- },
- { path: '', redirectTo: '/heroes', pathMatch: 'full' },
- { path: '**', component: PageNotFoundComponent }
- ];
-
- @NgModule({
- imports: [
- RouterModule.forRoot(
- appRoutes,
- { enableTracing: true } // <-- debugging="" only="" purposes="" span="">-->
- )
- ],
- exports: [
- RouterModule
- ],
- providers: [
- CanDeactivateGuard
- ]
- })
- export class AppRoutingModule {}
Resolve: pre-fetching component data
In the
Hero Detail
and Crisis Detail
, the app waited until the route was activated to fetch the respective hero or crisis.
This worked well, but there's a better way. If you were using a real world API, there might be some delay before the data to display is returned from the server. You don't want to display a blank component while waiting for the data.
It's preferable to pre-fetch data from the server so it's ready the moment the route is activated. This also allows you to handle errors before routing to the component. There's no point in navigating to a crisis detail for an
id
that doesn't have a record. It'd be better to send the user back to the Crisis List
that shows only valid crisis centers.
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
You need a resolver.
Fetch data before navigating
At the moment, the
CrisisDetailComponent
retrieves the selected crisis. If the crisis is not found, it navigates back to the crisis list view.
The experience might be better if all of this were handled first, before the route is activated. A
CrisisDetailResolver
service could retrieve a Crisis
or navigate away if the Crisis
does not exist beforeactivating the route and creating the CrisisDetailComponent
.
Create the
crisis-detail-resolver.service.ts
file within the Crisis Center
feature area.
- import { Injectable } from '@angular/core';
- import { Router, Resolve, RouterStateSnapshot,
- ActivatedRouteSnapshot } from '@angular/router';
- import { Observable } from 'rxjs';
- import { map, take } from 'rxjs/operators';
-
- import { Crisis, CrisisService } from './crisis.service';
-
- @Injectable()
- export class CrisisDetailResolver implements Resolve<Crisis> {
- constructor(private cs: CrisisService, private router: Router) {}
-
- resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> {
- let id = route.paramMap.get('id');
-
- return this.cs.getCrisis(id).pipe(
- take(1),
- map(crisis => {
- if (crisis) {
- return crisis;
- } else { // id not found
- this.router.navigate(['/crisis-center']);
- return null;
- }
- })
- );
- }
- }
Take the relevant parts of the crisis retrieval logic in
CrisisDetailComponent.ngOnInit
and move them into the CrisisDetailResolver
. Import the Crisis
model, CrisisService
, and the Router
so you can navigate elsewhere if you can't fetch the crisis.
Be explicit. Implement the
Resolve
interface with a type of Crisis
.
Inject the
CrisisService
and Router
and implement the resolve()
method. That method could return a Promise
, an Observable
, or a synchronous return value.
The
CrisisService.getCrisis
method returns an observable, in order to prevent the route from loading until the data is fetched. The Router
guards require an observable to complete
, meaning it has emitted all of its values. You use the take
operator with an argument of 1
to ensure that the observable completes after retrieving the first value from the observable returned by the getCrisis
method. If it doesn't return a valid Crisis
, navigate the user back to the CrisisListComponent
, canceling the previous in-flight navigation to the CrisisDetailComponent
.
Import this resolver in the
crisis-center-routing.module.ts
and add a resolve
object to the CrisisDetailComponent
route configuration.
Remember to add the
CrisisDetailResolver
service to the CrisisCenterRoutingModule
's providers
array.import { CrisisDetailResolver } from './crisis-detail-resolver.service';
@NgModule({
imports: [
RouterModule.forChild(crisisCenterRoutes)
],
exports: [
RouterModule
],
providers: [
CrisisDetailResolver
]
})
export class CrisisCenterRoutingModule { }
The
CrisisDetailComponent
should no longer fetch the crisis. Update the CrisisDetailComponent
to get the crisis from the ActivatedRoute.data.crisis
property instead; that's where you said it should be when you re-configured the route. It will be there when the CrisisDetailComponent
ask for it.ngOnInit() {
this.route.data
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}
Three critical points
- The router's
Resolve
interface is optional. TheCrisisDetailResolver
doesn't inherit from a base class. The router looks for that method and calls it if found. - Rely on the router to call the resolver. Don't worry about all the ways that the user could navigate away. That's the router's job. Write this class and let the router take it from there.
- The observable provided to the Router must complete. If the observable does not complete, the navigation will not continue.
Query parameters and fragments
In the route parameters example, you only dealt with parameters specific to the route, but what if you wanted optional parameters available to all routes? This is where query parameters come into play.
Fragments refer to certain elements on the page identified with an
id
attribute.
Update the
AuthGuard
to provide a session_id
query that will remain after navigating to another route.
Add an
anchor
element so you can jump to a certain point on the page.
Add the
NavigationExtras
object to the router.navigate
method that navigates you to the /login
route.import { Injectable } from '@angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanActivateChild,
NavigationExtras
} from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this.canActivate(route, state);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Create a dummy session id
let sessionId = 123456789;
// Set our navigation extras object
// that contains our global query params and fragment
let navigationExtras: NavigationExtras = {
queryParams: { 'session_id': sessionId },
fragment: 'anchor'
};
// Navigate to the login page with extras
this.router.navigate(['/login'], navigationExtras);
return false;
}
}
You can also preserve query parameters and fragments across navigations without having to provide them again when navigating. In the
LoginComponent
, you'll add an object as the second argument in the router.navigate
function and provide the queryParamsHandling
and preserveFragment
to pass along the current query parameters and fragment to the next route.// Set our navigation extras object
// that passes on our global query params and fragment
let navigationExtras: NavigationExtras = {
queryParamsHandling: 'preserve',
preserveFragment: true
};
// Redirect the user
this.router.navigate([redirect], navigationExtras);
The
queryParamsHandling
feature also provides a merge
option, which will preserve and combine the current query parameters with any provided query parameters when navigating.
Since you'll be navigating to the Admin Dashboard route after logging in, you'll update it to handle the query parameters and fragment.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
template: `
Dashboard
Session ID: {{ sessionId | async }}
<a id="anchor"></a>
Token: {{ token | async }}
`
})
export class AdminDashboardComponent implements OnInit {
sessionId: Observable ;
token: Observable ;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Capture the session ID if available
this.sessionId = this.route
.queryParamMap
.pipe(map(params => params.get('session_id') || 'None'));
// Capture the fragment if available
this.token = this.route
.fragment
.pipe(map(fragment => fragment || 'None'));
}
}
Query parameters and fragments are also available through the
ActivatedRoute
service. Just like route parameters, the query parameters and fragments are provided as an Observable
. The updated Crisis Admincomponent feeds the Observable
directly into the template using the AsyncPipe
.
Now, you can click on the Admin button, which takes you to the Login page with the provided
queryParamMap
and fragment
. After you click the login button, notice that you have been redirected to the Admin Dashboard
page with the query parameters and fragment still intact in the address bar.
You can use these persistent bits of information for things that need to be provided across pages like authentication tokens or session ids.
The
query params
and fragment
can also be preserved using a RouterLink
with the queryParamsHandling
and preserveFragment
bindings respectively.
Comments
Post a Comment