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:
CanActivateto mediate navigation to a route.CanActivateChildto mediate navigation to a child route.CanDeactivateto mediate navigation away from the current route.Resolveto perform route data retrieval before route activation.CanLoadto mediate navigation to a feature module loaded asynchronously.
You can have multiple guards at every level of a routing hierarchy. The router checks the
CanDeactivateand CanActivateChild guards first, from the deepest child route to the top. Then it checks the CanActivateguards 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 RouterStateSnapshotcontains 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 providersarray. 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.tsand 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
Resolveinterface is optional. TheCrisisDetailResolverdoesn'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
queryParamMapand fragment. After you click the login button, notice that you have been redirected to the Admin Dashboardpage 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