Contribute to help us improve!
Are there edge cases or problems that we didn't consider? Is there a technical pitfall that we should add? Did we miss a comma in a sentence?
If you have any input for us, we would love to hear from you and appreciate every contribution. Our goal is to learn from projects for projects such that nobody has to reinvent the wheel.
Let's collect our experiences together to make room to explore the novel!
To contribute click on Contribute to this page on the toolbar.
Routing
A basic introduction to the Angular Router can be found in Angular Docs.
This guide will show common tasks and best practices.
Defining Routes
For each feature module and the app module all routes should be defined in a separate module with the suffix RoutingModule
.
This way the routing modules are the only place where routes are defined.
This pattern achieves a clear separation of concerns.
The following figure illustrates this.
It is important to define routes inside app routing module with .forRoot()
and in feature routing modules with .forChild()
.
Example 1 - No Lazy Loading
In this example two modules need to be configured with routes - AppModule
and FlightModule
.
The following routes will be configured
-
/
will redirect to/search
-
/search
displaysFlightSearchComponent
(FlightModule
) -
/search/print/:flightId/:date
displaysFlightPrintComponent
(FlightModule
) -
/search/details/:flightId/:date
displaysFlightDetailsComponent
(FlightModule
) -
All other routes will display
ErrorPage404
(AppModule
)
const routes: Routes = [
{ path: '', redirectTo: 'search', pathMatch: 'full' },
{ path: '**', component: ErrorPage404 }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
const routes: Routes = [
{
path: 'search', children: [
{ path: '', component: FlightSearchComponent },
{ path: 'print/:flightId/:date', component: FlightPrintComponent },
{ path: 'details/:flightId/:date', component: FlightDetailsComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class FlightSearchRoutingModule { }
The import order inside AppModule is important.
AppRoutingModule needs to be imported after FlightModule .
|
Example 2 - Lazy Loading
Lazy Loading is a good practice when the application has multiple feature areas and a user might not visit every dialog. Or at least he might not need every dialog up front.
The following example will configure the same routes as example 1 but will lazy load FlightModule
.
const routes: Routes = [
{ path: '/search', loadChildren: 'app/flight-search/flight-search.module#FlightSearchModule' },
{ path: '**', component: ErrorPage404 }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
const routes: Routes = [
{
path: '', children: [
{ path: '', component: FlightSearchComponent },
{ path: 'print/:flightId/:date', component: FlightPrintComponent },
{ path: 'details/:flightId/:date', component: FlightDetailsComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class FlightSearchRoutingModule { }
Triggering Route Changes
With Angular you have two ways of triggering route changes.
-
Declarative with bindings in component HTML templates
-
Programmatic with Angular
Router
service inside component classes
On the one hand, architecture-wise it is a much cleaner solution to trigger route changes in Smart Components. This way you have every UI event that should trigger a navigation handled in one place - in a Smart Component. It becomes very easy to look inside the code for every navigation, that can occur. Refactoring is also much easier, as there are no navigation events "hidden" in the HTML templates
On the other hand, in terms of accessibility and SEO
it is a better solution to rely on bindings in the view - e.g. by using Angular router-link directive.
This way screen readers and the Google crawler can move through the page easily.
If you do not have to support accessibility (screen readers, etc.) and to care about SEO (Google rank, etc.),
then you should aim for triggering navigation only in Smart Components.
|
Guards
Guards are Angular services implemented on routes which determines whether a user can navigate to/from the route. There are examples below which will explain things better. We have the following types of Guards:
-
CanActivate
: It is used to determine whether a user can visit a route. The most common scenario for this guard is to check if the user is authenticated. For example, if we want only logged in users to be able to go to a particular route, we will implement theCanActivate
guard on this route. -
CanActivateChild
: Same as above, only implemented on child routes. -
CanDeactivate
: It is used to determine if a user can navigate away from a route. Most common example is when a user tries to go to a different page after filling up a form and does not save/submit the changes, we can use this guard to confirm whether the user really wants to leave the page without saving/submitting. -
Resolve: For resolving dynamic data.
-
CanLoad
: It is used to determine whether an Angular module can be loaded lazily. Example below will be helpful to understand it.
Let’s have a look at some examples.
Example 1 - CanActivate
and CanActivateChild
guards
CanActivate
guard
As mentioned earlier, a guard is an Angular service and services are simply TypeScript
classes. So we begin by creating a class. This class has to implement the CanActivate
interface (imported from angular/router
), and therefore, must have a canActivate
function. The logic of this function determines whether the requested route can be navigated to or not. It returns either a Boolean value or an Observable
or a Promise
which resolves to a Boolean value. If it is true, the route is loaded, else not.
CanActivate
example...
import {CanActivate} from "@angular/router";
@Injectable()
class ExampleAuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
canActivate(route: ActivatedRouterSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn()) {
return true;
} else {
window.alert('Please log in first');
return false;
}
}
}
In the above example, let’s assume we have a AuthService
which has a isLoggedIn()
method which returns a Boolean value depending on whether the user is logged in. We use it to return true
or false
from the canActivate
function.
The canActivate
function accepts two parameters (provided by Angular). The first parameter of type ActivatedRouterSnapshot
is the snapshot of the route the user is trying to navigate to (where the guard is implemented); we can extract the route parameters from this instance. The second parameter of type RouterStateSnapshot
is a snapshot of the router state the user is trying to navigate to; we can fetch the URL
from it’s url
property.
We can also redirect the user to another page (maybe a login page) if the authService returns false. To do that, inject Router and use it’s navigate function to redirect to the appropriate page.
|
Since it is a service, it needs to be provided in our module:
@NgModule({
...
providers: [
...
ExampleAuthGuard
]
})
Now this guard is ready to use on our routes. We implement it where we define our array of routes in the application:
...
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'page1', component: Page1Component, canActivate: [ExampleAuthGuard] }
];
As you can see, the canActivate
property accepts an array of guards. So we can implement more than one guard on a route.
CanActivateChild
guard
To use the guard on nested (children) routes, we add it to the canActivateChild
property like so:
...
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'page1', component: Page1Component, canActivateChild: [ExampleAuthGuard], children: [
{path: 'sub-page1', component: SubPageComponent},
{path: 'sub-page2', component: SubPageComponent}
] }
];
Example 2 - CanLoad
guard
Similar to CanActivate
, to use this guard we implement the CanLoad
interface and overwrite it’s canLoad
function. Again, this function returns either a Boolean value or an Observable
or a Promise
which resolves to a Boolean value. The fundamental difference between CanActivate
and CanLoad
is that CanLoad
is used to determine whether an entire module can be lazily loaded or not. If the guard returns false
for a module protected by CanLoad
, the entire module is not loaded.
CanLoad
example...
import {CanLoad, Route} from "@angular/router";
@Injectable()
class ExampleCanLoadGuard implements CanLoad {
constructor(private authService: AuthService) {}
canLoad(route: Route) {
if (this.authService.isLoggedIn()) {
return true;
} else {
window.alert('Please log in first');
return false;
}
}
}
Again, let’s assume we have a AuthService
which has a isLoggedIn()
method which returns a Boolean value depending on whether the user is logged in. The canLoad
function accepts a parameter of type Route which we can use to fetch the path a user is trying to navigate to (using the path
property of Route
).
This guard needs to be provided in our module like any other service.
To implement the guard, we use the canLoad
property:
...
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'admin', loadChildren: 'app/admin/admin.module#AdminModule', canLoad: [ExampleCanLoadGuard] }
];