My goal: I would like to globally listen for router NavigationEnd events and change the title of the web page depending on the route custom data.
Current code (in app.component.ts):
this.router.events.pipe(
filter((ev) => ev instanceof NavigationEnd),
map(() => this.activatedRoute),
map((route) => {
while (route.firstChild) route = route.firstChild;
return route;
}),
filter((route) => route.outlet === 'primary'),
mergeMap((route) => route.data),
).subscribe((event) => this.title.setTitle(event['title']));
got from this tutorial
The above code works, but is not elegant at all, look at the needed routing configuration:
app-routing.module.ts
const routes: Routes = [
{
path: 'user',
loadChildren: () => import(`./user/user.module`).then(m => m.UserModule),
data: { title: "Pagina Utente" }
},
{
path: 'admin', loadChildren: () => import(`./admin/admin.module`).then(m => m.AdminModule),
data: { title: "Pannello Admin" }
},
{ path: '**', component: HomeComponent },
];
admin-routing.module.ts:
const routes: Routes = [
{
path: '', component: AdminPanelComponent,
children: [
{
path: 'users', component: UsersComponent,
data: { title: 'Pannello Admin: Utenti' }
},
{
path: 'comuni', component: ComuniComponent,
data: { title: 'Pannello Admin: Comuni' }
},
]
}
];
As you can see, i already specified that the /admin route should have "Pannello Admin" as a title, but i need to write it again and again for each of its children routes.
I tried to fix this problem by changing the app.component.ts code to this:
this.router.events.pipe(
filter((ev) => ev instanceof NavigationEnd),
map(() => this.activatedRoute),
map((route) => {
let title = route.data['title'] || '';
while (route.firstChild) {
route = route.firstChild;
title+= route.data['title'];
}
return route;
}),
filter((route) => route.outlet === 'primary'),
mergeMap((route) => route.data),
).subscribe((event) => this.title.setTitle(event['title']));
I expected this code to work and set my title to the concatenation of the data['title'] property of the matched routes. However it doesn't work and just gives out empty titles (the data['title'] are always undefined for some reason)
Since Angular 14, this can be handled natively.
So, you can write the routes like below.
app-routing.module.ts
const routes: Routes = [
{
path: 'user',
loadChildren: () => import(`./user/user.module`).then(m => m.UserModule),
title: "Pagina Utente" // <--
},
{
path: 'admin', loadChildren: () => import(`./admin/admin.module`).then(m => m.AdminModule),
title: "Pannello Admin" // <--
},
{ path: '**', component: HomeComponent },
];
admin-routing.module.ts:
const routes: Routes = [
{
path: '', component: AdminPanelComponent,
children: [
{
path: 'users', component: UsersComponent,
title: 'Utenti' // <--
},
{
path: 'comuni', component: ComuniComponent,
title: 'Comuni' // <--
},
]
}
];
The default TitleStrategy
will result in showing the title of the end node of the routing tree. So, you'll see only Utenti
when you visit /admin/users
for example.
You can set 'Pannello Admin: Utenti'
to the title
property instead of 'Utenti'
, but overriding TitleStrategy
should be more effective.
You can see a simple sample code to override the strategy in Overriding The Global Title Strategy
section of the article here
To build titles like Pannello Admin: Utenti
from the routes above, the custom TitleStrategy
would be like:
@Injectable({providedIn: 'root'})
export class ConcatTitleStrategy extends TitleStrategy {
constructor(
private readonly title: Title
) {
super();
}
override updateTitle(routerState: RouterStateSnapshot): void {
const title = this.concatTitle(routerState.root, '', ': ');
if (title) {
this.title.setTitle(title);
}
}
private concatTitle(route: ActivatedRouteSnapshot, title: string, separator: string): string {
if (!route)
return title;
const sub = route.data ? this.getResolvedTitleForRoute(route) : undefined;
if (sub) {
title = `${title}${separator}${sub}`;
}
title = this.concatTitles(route.children[0], title, separator);
return title;
}
}