Routing in Angular
Angular has a built-in router. When initializing a new project, the CLI asks if it should be included or not. If we opted for “No” initially, we can add routing module manually:
We can also configure routing in the AppModule
file:
The selected component will be rendered as a child of AppComponent
. We need to
specify the exact placement of that component:
The routes array should generally contain a wildcard route that handles all bad links. Usually, it would redirect the user to some 404 page.
Paths of routes defined in the array are prefix paths by default. It means
that Angular will match it whenever the beginning of browser’s URL matches the
route’s path. If we want the route to be selected only when the whole URL is
matched (without the host though), we should add the pathMatch: 'full'
option.
External File
Usually, we do not define routes directly in app.module.ts
. Instead, we’d
create a separate module file app-routing.module.ts
with the routing setup
inside of it.
Then, the app.module.ts
file gets simplified:
We can also split routes per feature.
Navigation in HTML
With routing in place, we don’t want to navigate between different pages in our
app with traditional <a href="/whatever">Whatever</a>
. Clicking such a link
will work and the user will be taken to the right component (if the /whatever/
route was configured), but it comes with a huge issue - the whole application
will actually be reloaded from the server again. The app’s state will be lost,
and it will be slow. Instead of that, we want the Angular Router to handle the
navigation, making it an in-app navigation rather than a browser-based
navigation. Here’s how <a>
elements should look like:
The link above is applied to the host of the page. If we didn’t include the /
,
the home
segment would be applied to the currently open page. It works
differently in Programmatic Navigation!
Styling
In order to have visual indication on the currently visited menu element, we
would normally attach some CSS class to that active element. Angular comes with
a helper directive that does that automatically - routerLinkActive
.
The element that has the directive on it will have the “active” class attached
to it when the link is active. The directive can be attached on the <a>
or on
some element that wraps it, like in the example above.
Navigation in TypeScript
Router is accessible via TS as well.
The path that we navigate to is (by default) relative to the root.
Having or not having slash in the beginning does not change anything (it does
matter with routerLink)! We can change the path that navigate
will be executed in relation to with relativeTo
. For example, we could pass to
it the currently activated route (ActivatedRoute
).
Route Inputs
Path Parameters
Route’s path can have parameters. In the code example at the top of this page,
movies/:movieId
is an example of that. movieId
is a parameter, and the
MovieComponent
will receive it.
ActivatedRoute
We can get information about currently loaded route by injecting
ActivatedRoute
. It contains various metadata about the loaded path, e.g. the
parameters.
The parameters may also be subscribed to via an Observable
-
this.route.params
. It might be useful when we plan to link from some site to
itself with different parameters. In such a case, Angular will not reload the
whole component for optimization. Instead, only the ActivatedRoute
will
change.
Query Parameters
We can also make use of query parameters. To attach them to links on our page, we do it as follows:
Here’s how we add query params from TS:
Here’s how we can read query params from TS by injecting ActivatedRoute
(route
variable):
Similarly to path parameters, we can also subscribe to this.route.queryParams
.
Preserving Query Params
When we’re on some page with some query params in the URL, by default these query params will be removed when we navigate to another page. If we don’t want that, we can do it the following way:
The merge
handling merges together existing query params and those that we
might want to add (by having queryParams
defined).
Fragment
Similarly, we can attach fragment (#fragment
) to link we navigate to:
Here’s how we add fragment from TS:
Here’s how we can read fragment from TS by injecting ActivatedRoute
(route
variable):
Similarly to path parameters, we can also subscribe to this.route.fragment
.
Static Data
Routes can have some static data defined. This way, the same route can be reused
multiple times. For example, we could have a generic ErrorComponent
which
displays different message depending on the kind of error. Such a component
could look like this:
We can set the static value(s) in the routes collection:
Nested Routing
Nested Routing allows us to have multiple routing outlets, one within another. We could have a main menu with each entry of it loading a different submenu. Then, each submenu would have a list of links that load a different content.
First, we need to set up our routes properly:
In the example above, /:movieId
is a nested route. The full path (without
host) to it is /movies/:moviesId
.
The next thing to do is to place an outlet where the MovieComponent
will be
rendered. We should place it somewhere within MoviesComponent
:
With this setup, when navigating to /movies/<id>
, the following will happen:
- the
MoviesComponent
will be rendered in therouter-outlet
within theAppComponent
- the
MovieComponent
will be rendered in therouter-outlet
within theMoviesComponent
The setup could be more complex, having multiple children under the movies
path, or by having more levels of nesting.
Guards
Routes can be protected by Guards. The user may be either allowed or disallowed to enter some content. It could be due to them being (un)authorized in some way.
Guards are services and we normally store them in *.service.ts
files.
Here’s a simple example:
The guard needs to implement CanActivate
. Its canActivate
method should
return one of:
bool | UrlTree
Observable<bool | UrlTree>
Promise<bool | UrlTree>
UrlTree
We can route users to some other page in the guard, most likely when the
condition is not satisfied. We use the UrlTree
for that - it’s one of the
types that may be returned from guards. Here’s how we’d return it:
The router
is an instance of a Router
.
The route should be enabled for selected endpoints in the routes definition:
The /users
endpoint is protected by our guard. If we applied the guard to the
/movies
route, the child of it would also use it.
CanDeactivate Guard
Similarly to checking if a user can enter some route, we can also check if they should be able to leave it. This is to protect users from unintentionally leaving half-done form, forgetting to save their work, etc.
Setting this up is a little bit more involved than using CanActivate
guard.
That’s because the guard will most likely need some input from the component
that we’re leaving to know if the user should be able to leave. The component
could have some isWorkSaved
variable, but the guard cannot reach it - it’s a
separate class after all. We can solve this problem using generics.
Here’s an example:
Next piece is the actual component’s code. The component needs to
implement DeactivatableComponent
:
To use the new guard, we need to enable it in the routes collection:
Resolvers
When a given component needs some external data to be loaded before it can be displayed, custom Resolvers can be used.
Here’s an example of such a resolver:
To use the resolver, we attach it to the route that needs it:
Here’s how we can access the result of resolver’s work in the MovieComponent
:
We subscribe to the result, because the resulting data could change when we reload the component (Angular will not reload the whole component for performance reasons).
The result is placed in this.route.data
object under the key that we used in
the route definition’s resolve
section (movie
in this case). The data
object was also used in the Static Data.
Lazy Loading
Lazy Loading is described in the Modules section.