Introduction
In the current frontend development approach, most web applications are SPA which stands for Single Page applications. It means the browser won't refresh the page whenever we redirect from one page to another. Instead, the proper content of the page is replaced. In this article, I will explain features placed in Angular and related to the routing mechanism.
Project creation
To create a new project in the command line, I type:
ng new books-app --standalone
And I answer y
to create Angular routing.
standalone
is a new flag added with version 14. It creates an application in a standalone mode, so for example, AppModule
is not created, and AppComponent
is used as an initial class.
Since we do not have AppModule
, We need a provider for routes, which are provided by using provideRouter
function inside the app.config.ts
file. Thankfully, that is auto-generated during the project initialization.
Route properties
Inside the app.routes.ts
file, we will find routes
constants with an empty array. For demo purposes, let's generate a component that will be routed to. We can do that by command:
ng g c library
-
g
is a shortcut forgenerate
-
c
is a shortcut forcomponent
-
library
is the name of the component
Since we defined our project as a standalone, that component is also generated as a standalone.
Path
Properties related to the path are:
path
- string represents a link to our component, so setting it tolibrary
allows us to visit that component under thehttp//:example.com/library
address.redirectTo
- string which will redirect the current path to the given route. For example, we can set the path to the wildcard**
to redirect to the given path inredirectTo
-
pathMatch
- can be set to one of the:-
full
- means that the whole URL path needs to match and is consumed by the route matching algorithm. -
prefix
means the first route where the path matches the start of the URL chosen. Still, the route matching algorithm continues searching for matching child routes where the rest of the URL matches.
-
matcher
- if we cannot define a route by a simple string, we can define a custom matcher that, for example, can check a given URL with a regex (we cannot use it with apath
property). Sample from the documentation:
{
matcher: (url) => {
if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) {
return {
consumed: url,
posParams: {
username: new UrlSegment(url[0].path.slice(1), {})
}
};
}
return null;
},
component: ProfileComponent
}
Component
Properties related to the component are:
-
loadComponent
- is used to lazy load selected components whenever the given route is visited. Great for optimization because it creates a dedicated bundle file for a specific component, so the initial bundle file is smaller; example value can be:() => import('./library.component').then(m => m.LibraryComponent)
, -
component
- like above, but without lazy loading example value:LibraryComponent
, -
children
- array with nested routes under a given route, -
loadChildren
- like above but for modules.
Guards and resolvers
In Angular, Guards are used to protect routes, for example, if a user is not allowed to visit some of them. To create a guard, we can use CLI by typing:
ng g g admin-only
It can be set to one (or more) of possible types:
-
CanActivate
- which loads component only when the given guards return true -
CanActivateChild
- which loads children of the given routes only whenever the provided guard returns true, but it won't check the component set to the given route -
CanDeactivate
- which checks if we are allowed to leave the specific route -
CanMatch
- introduced with version 14 of the angular, checks if we can enter a given route. If not, then proceeds to the next one that matches the current path (inCanActive
, it would redirect back). Example content of the guard can be:
export const adminOnlyGuard: CanMatchFn = (route, segments) => {
const user = localStorage.getItem('user');
if (!user) {
localStorage.setItem('user', 'John Doe');
return false;
}
return true;
};
which, for the first time, won't let the user in because there is no user data yet, but after a second attempt, it will allow them to enter.
To use the generated guard, we have to apply it in appropriate property or more than one if needed, for example:
canActivate: [adminOnlyGuard],
canMatch: [adminOnlyGuard],
Resolvers are used to provide data to the component that is set in the current route. It will wait for the result from that resolver and then loads the component, so thanks to that, we are sure that the required values will be there. To generate a resolver, we can use CLI once again:
ng g r user-data
This will create a method that is used to retrieve data. Example content could be:
export const userDataResolver: ResolveFn<string> = (route, state) => {
return of('mail@example.com').pipe(delay(3000))
};
of
is an RxJs function to create an observable, and a delay
is a function to delay the emission from the observable by a given time.
To use the generated resolver, we have to add it to the resolver property:
resolve: [userDataResolver]
To reach that data from the component, we can use the ActivatedRoute
snapshot instance, for example:
private readonly route = inject(ActivatedRoute);
constructor() {
console.log(this.route.snapshot.data);
}
To distinguish whether to run resolvers or guards, we can use the runGuardsAndResolvers
property, which can be set to one of the:
-
always
- which will always run -
pathParamsChange
- whenever path params are changed -
paramsOrQueryParamsChange
- whenever path, matrix, or query parameters change -
pathParamsOrQueryParamsChange
- Rerun guards and resolvers when the path params change, or query params have changed.
Data
To pass static data to the component, we can use the data
property and pass any data as a value, for example:
data: {
prop: 'value'
}
To reach that data from the component, we can use ActivatedRoute
like above.
The title
property sets the page title of a given route.
The providers
property defines instances of providers, for example, services.
We can also pass data as route segments. To do that, in the path
property, we can use a colon : NAME
, for example, library/:id
, which then in route /library/1
sets key id
and a value 1
. That value can be found in the ActivatedRoute
but in the snapshot.params
property.
Outlet
To display the component assigned to the route in the given parent component, we are placing the following:
<router-outlet></router-outlet>
That will render the contents of the component in the selected place.
We can have more than one router-outlet
, and then we can select where to render the view by adding a name attribute to the html part:
<router-outlet name="secondary"></router-outlet>
and by setting route property:
outlet: 'secondary'
Route navigations
We can change the current route from HTML by using the routerLink
attribute and setting it to the required path, for example:
<a href="" routerLink="/other">Go to other</a>
and from the typescript code part by using Router
and calling:
private readonly router = inject(Router);
gotToOther() {
this.router.navigate(['other']);
}
Summary
The angular framework has a powerful routing mechanism and can be configured in many ways. I introduced some basics in this article and encourage You to dive deep into the official documentation.
Top comments (0)