Binding Router Information to Routed Component Inputs in Angular
Angular v16 has introduced a powerful new feature that enables the automatic binding of router information, such as query parameters, path parameters, static data, and resolver data to a routed component’s inputs.
This functionality can be activated using the withComponentInputBinding
function. Here’s an example of how to use it:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withComponentInputBinding } from '@angular/router';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(
[
{
path: '',
pathMatch: 'full',
loadComponent: () => import('./app/home/home.component'),
},
{
path: 'todo',
loadComponent: () => import('./app/todo/todo.component'),
},
],
// 👇👇👇
withComponentInputBinding()
),
],
});
With this feature, the router outlet
component listens to the activated route query parameters, path parameters, and data observables and automatically calls the setInput
method with the initial value and when it changes.
For example, suppose we have an id
query parameter in our URL and want to fetch the current entity. In this case, our URL will be /todo?id=1
, and our component will look like this:
@Component({
...
standalone: true,
})
export default class TodoComponent {
private todosService = inject(TodosService);
todo$: Observable<Todo>;
// The input name should be the same as the query param key
@Input() set id(value: string) {
this.todo$ = this.todosService.get(value);
}
}
Similarly, if we have a search term in our URL (e.g /foo?searchTerm=bar
), we can update our form control with its value:
@Component({
...
standalone: true,
imports: [ReactiveFormsModule],
})
export default class FooComponent {
searchControl: FormControl<string>;
@Input() searchTerm: string;
ngOnInit() {
this.searchControl = new FormControl(this.searchTerm || '');
}
}
In cases of conflicts, the priority is set based on the following: data > path parameters > query parameters.
Here’s an example that demonstrates all the valid use cases:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(
[
{
path: 'todos/:todoId',
data: {
isSomething: true,
},
resolve: { resolveFoo: () => 'My resolved data' },
loadComponent: () => import('./app/todo/todo.component'),
},
],
withComponentInputBinding()
),
],
});
Suppose our URL is /todos/1?searchTerm=angular
, and our component looks like this:
@Component({
...
standalone: true,
})
export default class TodoComponent {
@Input() resolveFoo: string; // My resolved data
@Input() isSomething: boolean; // true
@Input() todoId: string; // 1
@Input() searchTerm: string; // angular
}
Overall, this new feature streamlines the process of passing router information to routed components, and reducing the need for boilerplate code.
Follow me on Medium or Twitter to read more about Angular and JS!