May 7th, 2021 - written by Kimserey with .
Angular Router reuses components when it sees fit. The act of reusing a component saves time as the framework doesn’t need to destroy and create back a new object, instead it emits new values onto the observable params
and data
of the route. In today’s post we dig into the details of when does the reusability of components take place and understand how it impacts route.data
.
The only time the router reuses a component is when the url changes but the route config does not. This occurs when the path
is defined with a param e.g. :id
.
1
2
3
4
5
6
7
8
9
10
11
12
13
const routes: Routes = [
{
path: '',
component: RouteMainComponent,
children: [
{
path: ':id',
data: { test: 1 },
resolve: { value: TestResolver },
component: RouteOneComponent,
},
]
}
In this example, RouteOneComponent
gets reused (not reinstantiated) when we move from /1
to /2
to /helloworld
.
In contrast:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const routes: Routes = [
{
path: '',
component: RouteMainComponent,
children: [
{
path: 'hello',
data: { test: 1 },
component: RouteOneComponent,
},
{
path: 'world',
data: { test: 1 },
component: RouteOneComponent,
},
]
}
Here /hello
and /world
are two separate routes and RouteOneComponent
gets reinstantiated every time we switch.
The reason why this is the case is because the default reuse strategy uses reference equality ===
to check whether it should destroy or not the component. This is defined in BaseRouteReuseStrategy
.
1
2
3
4
5
6
7
8
/**
* Determines if a route should be reused.
* This strategy returns `true` when the future route config and current route config are
* identical.
*/
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig;
}
So the only time the router reuses components is when the path
maps to the exact same route.
route.data
an observable?Now I was wondering why would route.data
have to be an observable. And to answer that we need to understand where route.data
gets populated. Route.data
can be filled up in two ways:
data: { value: 'hello' }
on the route,resolve: { value: TestResolver },
where TestResolver
is a service implementing Resolve<T>
interface.1
2
3
interface Resolve<T> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T
}
Before the route gets activated, it waits for the resolvers to complete and merge data
with the result of the resolvers. It’s important to note that the router waits for the completion of the resolver observable to assign the last value. So we can choose what we we prefer; showing progressively the component then the data loaded asynchronously, or have a blank component which only shows when everything is resolved.
We saw how components are being reused and we saw also that data
get be provided in two ways, static and via resolver. If there was only a static way to provide data
, there wouldn’t be a need for an Observable
as by definition they wouldn’t change (hardcoded on the route) - obviously provided that we don’t override the default reuse strategy.
So it is really the reusability of components that forces the route.data
to be an Observable
as the resolvers will be re-triggered on path
change which will give a chance to look into the ActivatedRouteSnapshot
from the resolve
function argument, and reload data accordingly.
And that concludes today’s post!
Today we looked into how the router reuses components. We digged into how the router decides to reuse a component or create a new one and we then moved on to understand the reason why route.data
had to be an observable
. I hope you liked this post and I see you on the next one!