Component Reuse Angular Angular

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.

Router Reusability of Components

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.

Why is 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:

  1. static route data from data: { value: 'hello' } on the route,
  2. and resolver from 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!

Conclusion

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!

Designed, built and maintained by Kimserey Lam.