My rookie Angular router mistake

My rookie Angular router mistake
Photo by JESHOOTS.COM / Unsplash

With the release of Angular 14, the ability to set the page title natively with the Angular Router became available.

In previous versions, the page title was set programatically, commonly using the data property of the router config as a property bag. This new feature simplifies this part considerably.

Route config

In my NX-powered application, I have the following the dependency graph.

App dependency graph

The main route is defined in the feature-shell. The feature routes are defined as children routes that are lazily-loaded by the feature-shell.

export const shellFeatureRoutes: Route[] = [
  {
    path: '',
    component: ShellFeatureComponent,
    children: [
      {
        path: 'expenses',
        loadChildren: () =>
          import('@snarbanking-workspace/expenses/feature-view').then(
            (m) => m.expensesFeatureViewRoutes
          ),
        title: 'Expenses'
      },
      ...
    ],
  },
];

Crucially, I added the title property in the route config above. Looking good so far.

In a separate ui component, ui-header, I displayed the page title as an H1 after it was derived by the feature-shell from NGRX router-store using the selector selectTitle.

// in shell.component.ts
export class ShellFeatureComponent {
  private store = inject(Store);
  pageTitle$ = this.store.select(selectTitle);
}
<!-- in shell.component.html -->

<div class="min-h-full">
  <snarbanking-workspace-shell-ui-nav />
  <snarbanking-workspace-shell-ui-header [headerText]="pageTitle$ | async" />
  ...
</div>

Surprisingly, instead of a title text, the page header was not displaying anything. What did I miss? In fact, it was undefined

title: undefined

I reviewed the NGRX documentation on how to register and configure the router-store. Surely, nothing should be wrong there, since no customizations have been applied yet, even the router-store feature name I used is the default,router.

Next, I logged the data in the current route being derived by the router-store into the console, using selectCurrentRoute, to see if it was able to see the title. It wasn't.

{"params":{},"data":{},"url":[],"outlet":"primary","routeConfig":{"path":""},"queryParams":{},"fragment":null,"children":[]}

Next, I opted to configure the custom router serializer as detailed here, adding the title property, to see if that could make a difference. Note that when a custom serializer is set, the default router selectors will have to be provided.

provideRouterStore({
  serializer: CustomRouterSerializer,
}),

It did not.

Checking the NGRX GitHub Issues list, I didn't see anything that resembles the issue that I was facing. Besides, surely getting the title from the route config should not be this involved to the point that a customized serializer and custom router-selectors need to be created. I must have configured something incorrectly.

True enough, after re-reading and re-inspecting my route configs, I found out that the title had to be specified at the deepest route config with the title property, instead of at the point where the lazy-loaded import is specified.

{
  path: 'expenses',
  loadChildren: () =>
    import('@snarbanking-workspace/expenses/feature-view').then(
      (m) => m.expensesFeatureViewRoutes
    ),
  title: 'Expenses' // not here
}

The Angular documentation about TitleStrategy had this to say:

The built-in implementation traverses the router state snapshot and finds the deepest primary outlet with titleproperty.
{
  path: '',
  component: ExpensesFeatureViewComponent,
  title: 'Expenses' // but here
}

Set properly, pageTitle$ = this.store.select(selectTitle); should work like a charm!

In summary, we utilize the default selectTitle router-selector to get the page title properly by making sure to set the title route config at the deepest or at the leaf node route. If not, then the title will be undefined.

via GIPHY

Lhar Gil

Lhar Gil

Tech-savvy software developer and street photography enthusiast. Exploring the world through code and candid shots of daily life. 📸 All opinions are my own.
England