Skip to content

Commit 08cede1

Browse files
authored
Merge pull request #602 from max-scopp/feature/injector-using-navigation-end
feat(inject-leaf-activated-route): add injectLeafActivatedRoute, and .global() in injectParams and injectRouterData
2 parents e2aa13f + a86b94c commit 08cede1

File tree

14 files changed

+2534
-117
lines changed

14 files changed

+2534
-117
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
---
2+
title: injectLeafActivatedRoute
3+
description: ngxtension/inject-leaf-activated-route
4+
entryPoint: ngxtension/inject-leaf-activated-route
5+
badge: stable
6+
contributors: ['max-scopp']
7+
---
8+
9+
:::note[Router outlet is required]
10+
`injectLeafActivatedRoute` works on all components that are inside routing context. Make sure the component you are using `injectLeafActivatedRoute` in is part of your routes.
11+
:::
12+
13+
`injectLeafActivatedRoute` is a helper function that returns a signal containing the deepest (leaf) activated route in the router state tree.
14+
15+
The leaf route is the deepest child route that has no children of its own. This is useful when you need to access route information from the currently active, deepest route regardless of your component's position in the route hierarchy.
16+
17+
Having the leaf route as a signal helps in a modern Angular signals-based architecture and automatically updates whenever navigation ends.
18+
19+
```ts
20+
import { injectLeafActivatedRoute } from 'ngxtension/inject-leaf-activated-route';
21+
```
22+
23+
## Usage
24+
25+
### Get the leaf activated route
26+
27+
`injectLeafActivatedRoute` when called, returns a signal with the current leaf activated route.
28+
29+
```ts
30+
@Component({
31+
standalone: true,
32+
template: `
33+
<div>Current route: {{ leafRoute().snapshot.url }}</div>
34+
<div>Route params: {{ leafRoute().snapshot.params | json }}</div>
35+
`,
36+
})
37+
class MyComponent {
38+
leafRoute = injectLeafActivatedRoute();
39+
}
40+
```
41+
42+
### Access route parameters from leaf route
43+
44+
The most common use case is to access route parameters from the deepest active route, regardless of where your component is in the component tree.
45+
46+
```ts
47+
@Component({
48+
template: `
49+
@if (user(); as user) {
50+
<div>{{ user.name }}</div>
51+
} @else {
52+
<div>Loading...</div>
53+
}
54+
`,
55+
})
56+
class ParentComponent {
57+
leafRoute = injectLeafActivatedRoute();
58+
59+
// Access the 'id' param from the leaf route
60+
userId = computed(() => this.leafRoute().snapshot.params['id']);
61+
}
62+
```
63+
64+
### Access multiple route parameters
65+
66+
```ts
67+
@Component({
68+
template: `
69+
<div>Organization: {{ orgId() }}</div>
70+
<div>User: {{ userId() }}</div>
71+
`,
72+
})
73+
class DashboardComponent {
74+
leafRoute = injectLeafActivatedRoute();
75+
76+
orgId = computed(() => this.leafRoute().snapshot.params['orgId']);
77+
userId = computed(() => this.leafRoute().snapshot.params['userId']);
78+
}
79+
```
80+
81+
### Access query parameters
82+
83+
You can also access query parameters from the leaf route:
84+
85+
```ts
86+
@Component({
87+
template: `
88+
<div>Search query: {{ searchQuery() }}</div>
89+
`,
90+
})
91+
class SearchComponent {
92+
leafRoute = injectLeafActivatedRoute();
93+
94+
searchQuery = computed(
95+
() => this.leafRoute().snapshot.queryParams['query'] ?? '',
96+
);
97+
}
98+
```
99+
100+
### Access route data
101+
102+
Access static or resolved data from the leaf route:
103+
104+
```ts
105+
@Component({
106+
template: `
107+
<div>Requires auth: {{ requiresAuth() }}</div>
108+
`,
109+
})
110+
class AdminComponent {
111+
leafRoute = injectLeafActivatedRoute();
112+
113+
requiresAuth = computed(
114+
() => this.leafRoute().snapshot.data['requiresAuth'] ?? false,
115+
);
116+
}
117+
```
118+
119+
## Why use this over `inject(ActivatedRoute)`?
120+
121+
When you inject `ActivatedRoute` directly, you get the route associated with the current component. This might not be the deepest route if you have nested routes with child components.
122+
123+
`injectLeafActivatedRoute` always gives you the deepest active route, which is often what you need when you want to access parameters from the currently displayed page, regardless of your component's position in the route hierarchy.
124+
125+
### Example scenario
126+
127+
Consider this route structure:
128+
129+
```
130+
/dashboard/:orgId/users/:userId
131+
```
132+
133+
With this component hierarchy:
134+
135+
```
136+
DashboardComponent (at /dashboard/:orgId)
137+
└─ UsersComponent (at users/:userId)
138+
```
139+
140+
In `DashboardComponent`, if you use `inject(ActivatedRoute)`, you only get access to `orgId`. But with `injectLeafActivatedRoute()`, you can access both `orgId` and `userId` because it gives you the deepest route.
141+
142+
## Reactive updates
143+
144+
The signal automatically updates whenever navigation ends, ensuring it always reflects the current leaf route:
145+
146+
```ts
147+
@Component({
148+
template: `
149+
<div>Current user ID: {{ userId() }}</div>
150+
<button (click)="navigateToUser('123')">User 123</button>
151+
<button (click)="navigateToUser('456')">User 456</button>
152+
`,
153+
})
154+
class UserListComponent {
155+
router = inject(Router);
156+
leafRoute = injectLeafActivatedRoute();
157+
158+
userId = computed(() => this.leafRoute().snapshot.params['id']);
159+
160+
navigateToUser(id: string) {
161+
this.router.navigate(['/users', id]);
162+
// The userId signal will automatically update after navigation
163+
}
164+
}
165+
```
166+
167+
## Use with other inject utilities
168+
169+
`injectLeafActivatedRoute` works great with other ngxtension utilities like `injectParams` and `injectQueryParams`, but it's particularly useful when you need access to the full route object or when building reusable components that need to be aware of the current route state.
170+
171+
```ts
172+
import { injectParams } from 'ngxtension/inject-params';
173+
174+
@Component({})
175+
class MyComponent {
176+
// Using injectParams with global option
177+
// internally uses injectLeafActivatedRoute
178+
userId = injectParams('id', { global: true });
179+
180+
// Or use injectLeafActivatedRoute directly for more control
181+
leafRoute = injectLeafActivatedRoute();
182+
allParams = computed(() => this.leafRoute().snapshot.params);
183+
}
184+
```

docs/src/content/docs/utilities/Injectors/inject-params.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ If we want to use a default value if there is no value, we can pass a `defaultVa
109109
`,
110110
})
111111
class TestComponent {
112-
angularVersion = injectParams('version', { defaultValue: '19' }); // returns a signal with the value of the id param parsed to a number
112+
angularVersion = injectParams('version', { defaultValue: '19' }); // returns a signal with the value of the version param or '19' if not present
113113

114114
angular = derivedFrom(
115115
[this.angularVersion],
@@ -119,3 +119,79 @@ class TestComponent {
119119
);
120120
}
121121
```
122+
123+
## Global Params
124+
125+
`injectParams.global()` allows you to access route params from the entire route hierarchy, including all parent and child routes. This is similar to Angular's `input()` and `input.required()` pattern.
126+
127+
:::note[Use Case]
128+
Use `.global()` when you need to access params from child routes or when your component needs to be aware of the full routing context, not just its own route params.
129+
:::
130+
131+
### Get all params from route hierarchy
132+
133+
```ts
134+
@Component()
135+
class ParentComponent {
136+
// Gets all params from parent and all child routes
137+
// Child params override parent params if they have the same name
138+
allParams = injectParams.global();
139+
}
140+
```
141+
142+
### Get specific param from route hierarchy
143+
144+
```ts
145+
@Component()
146+
class ParentComponent {
147+
// Gets the 'id' param from anywhere in the route hierarchy
148+
id = injectParams.global('id');
149+
}
150+
```
151+
152+
### Transform global params
153+
154+
```ts
155+
@Component()
156+
class ParentComponent {
157+
// Transform all params from the route hierarchy
158+
paramCount = injectParams.global((params) => Object.keys(params).length);
159+
}
160+
```
161+
162+
### With options
163+
164+
All the same options work with `.global()`:
165+
166+
```ts
167+
@Component()
168+
class TestComponent {
169+
// Parse param as number
170+
userId = injectParams.global('id', {
171+
parse: numberAttribute,
172+
});
173+
}
174+
```
175+
176+
### Example: Breadcrumbs
177+
178+
A common use case is building breadcrumbs that need access to all route params:
179+
180+
```ts
181+
@Component({
182+
template: `
183+
<nav>
184+
@for (crumb of breadcrumbs(); track crumb.path) {
185+
<a [routerLink]="crumb.path">{{ crumb.label }}</a>
186+
}
187+
</nav>
188+
<router-outlet />
189+
`,
190+
})
191+
class LayoutComponent {
192+
allParams = injectParams.global();
193+
194+
// Build breadcrumbs using all params from route hierarchy
195+
breadcrumbs = computed(() => buildBreadcrumbs(this.allParams()));
196+
}
197+
```

0 commit comments

Comments
 (0)