Repository navigation
How to use different layouts for /books and /books/:id in Angular? #25065
Replies: 3 comments
|
Hi! I looked into how Why |
| URL | Layout |
|---|---|
| /books | eLayoutType.application |
| /books/123 | eLayoutType.empty |
| /books/456 | eLayoutType.empty |
| Back to /books | eLayoutType.application |
Important consideration
This is a workaround, not a first-class ABP route configuration feature. It modifies RoutesService at runtime, so I would validate direct navigation, refresh, back/forward navigation, and parameter changes against your application's routing setup.
For a larger application with many parameterized routes requiring different layouts, a route-aware layout resolver would be a cleaner long-term solution.
The relevant ABP 10.1 source code is available here:
It might also be worth supporting parameterized route matching directly in ABP's layout resolver, so Angular's route definitions can determine layouts without runtime registration.
Hope this helps!
Hi!I looked into how DynamicLayoutComponent and findRoute() work in ABP 10.1. The behavior you're seeing comes from ABP's layout resolution rather than Angular's route matching.
Why data.layout doesn't work
ABP's DynamicLayoutComponent resolves its layout primarily through RoutesService.
Internally, findRoute() performs an exact path comparison:
const node = routesService.find(route => route.path === path);When no match is found, it recursively searches the parent path.
Therefore, for /books/123:
- ABP looks for
/books/123. - No exact match exists.
- It falls back to
/books. /booksdeclareseLayoutType.application.- The detail page inherits the application layout.
Adding /books/:id to RoutesService doesn't solve this either, because findRoute() doesn't interpret Angular parameterized route patterns.
Also, data.layout on the child Angular route doesn't override the layout selected by the root DynamicLayoutComponent.
Workaround: register the resolved detail path
If you must preserve both URLs, you can register an invisible ABP route for the actual detail URL while the detail component is active.
For example, in detail.component.ts:
import {
Component,
DestroyRef,
inject,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { RoutesService, eLayoutType } from '@abp/ng.core';
import { distinctUntilChanged, map } from 'rxjs';
const DETAIL_LAYOUT_ROUTE = '::Books:DetailLayout';
@Component({
selector: 'app-book-detail',
standalone: true,
templateUrl: './detail.component.html',
})
export class BookDetailComponent {
private readonly route = inject(ActivatedRoute);
private readonly routes = inject(RoutesService);
private readonly destroyRef = inject(DestroyRef);
constructor() {
this.route.paramMap
.pipe(
map(params => params.get('id')),
distinctUntilChanged(),
takeUntilDestroyed(this.destroyRef),
)
.subscribe(id => {
this.routes.remove([DETAIL_LAYOUT_ROUTE]);
if (!id) return;
this.routes.add([
{
name: DETAIL_LAYOUT_ROUTE,
path: `/books/${id}`,
layout: eLayoutType.empty,
invisible: true,
},
]);
});
this.destroyRef.onDestroy(() => {
this.routes.remove([DETAIL_LAYOUT_ROUTE]);
});
}
}You can keep your existing Angular route definitions and /books registration unchanged.
The temporary route is registered with the resolved URL, so ABP can find an exact match when it evaluates the layout on NavigationEnd.
The subscription also handles navigation between different book IDs without requiring the component to be recreated, and the cleanup prevents stale registrations when leaving the detail page.
Expected behavior
| URL | Layout |
|---|---|
/books |
eLayoutType.application |
/books/123 |
eLayoutType.empty |
/books/456 |
eLayoutType.empty |
Back to /books |
eLayoutType.application |
Important consideration
This is a workaround, not a first-class ABP route configuration feature. It modifies RoutesService at runtime, so I would validate direct navigation, refresh, back/forward navigation, and parameter changes against your application's routing setup.
For a larger application with many parameterized routes requiring different layouts, a route-aware layout resolver would be a cleaner long-term solution.
The relevant ABP 10.1 source code is available here:
- [DynamicLayoutComponent](https://github.com/abpframework/abp/blob/10.1.0/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts)
- [findRoute() implementation](https://github.com/abpframework/abp/blob/10.1.0/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts)
It might also be worth supporting parameterized route matching directly in ABP's layout resolver, so Angular's route definitions can determine layouts without runtime registration.
Hope this helps!
|
In ABP 10.1, the dynamic layout reads the RoutesService tree. Its route lookup uses exact paths, falling back to parent paths. For Registering One workaround that preserves your URLs is to register the concrete detail path before NavigationEnd, using a guard: import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { RoutesService, eLayoutType } from '@abp/ng.core';
export const bookDetailLayoutGuard: CanActivateFn = route => {
const id = route.paramMap.get('id');
if (id === null) return false;
inject(RoutesService).add([{
path: `/books/${id}`,
name: 'BookDetailLayout',
layout: eLayoutType.empty,
invisible: true,
}]);
return true;
};Add it to the detail route: canActivate: [bookDetailLayoutGuard],The stable name replaces the previous detail registration; |
|
Hi @snowchenlei, One additional consideration after revisiting the ABP 10.1 layout lifecycle: the workaround I suggested has a potential timing issue.
A more reliable approach would be to register the resolved detail route through an Angular Longer term, supporting parameterized route matching directly in ABP's Have you had a chance to test the original workaround? In particular, does the behavior differ between navigating from That would help determine whether a guard-based workaround is sufficient or whether the layout resolver itself needs to be addressed. |
Uh oh!
There was an error while loading. Please reload this page.
Abp Version: 10.1
I'm trying to implement a books list page and a detail page in my Angular application.
My requirement is:
/books → should use
eLayoutType.application/books/:id → should use
eLayoutType.emptyCurrently I have the following code.
Menu route configuration:
Angular routes:
I tried switching the layout in the route data for the detail page:
However, this does not seem to have any effect, and the page still uses the application layout.
What is the correct way to configure different layouts for these two routes?
Any guidance would be appreciated.
All reactions