RFC: Add utility for Router/State Binding
alcaidio opened this issue · comments
I have a feature proposal that I'd like to discuss with you all.
The idea is to create a utility to bind our application state with our router, particularly focusing on query parameters. This would enable synchronization, for instance, of filters with the route of an e-commerce site.
Consider the following interfaces:
interface Product {
id: number;
name: string;
brand: string;
category: string;
price: number;
}
interface FilterOptions {
brand: string[];
category: string[];
price: {
min: number;
max: number;
};
}
The route could then take the form:
https://example.com/products?brand=nike,adidas&category=shoes&price-min=30&price-max=90
What we aim for is to bind to our route declaratively without dealing with the activatedRoute.params observable directly. For example:
@Injectable({ providedIn: 'root' })
export class ProductService {
products = signal<Product[]>([]);
filters = signal<FilterOptions>({});
bindWithRouter(this.filters); // Naming and configuration to be considered...
}
Here's a proposed implementation for bindWithRouter, which would facilitate developing the feature (to be made generic with strict typing, of course):
router = inject(Router);
route = inject(ActivatedRoute);
constructor() {
const updateStateFromRoute$ = this.route.queryParams.pipe(
tap((queryParams) => {
const reducer = (acc: Filters, curr: [string, string]) => {
const [key, values] = curr;
return { ...acc, [key]: values.split(',') };
};
const filters = Object.entries(queryParams).reduce(
reducer,
{} as Filters
);
this.filters.set(filters);
})
);
const updateRouteFromState$ = toObservable(this.filters).pipe(
skip(1), // To skip initial state of filters and use state from queryparams if exist
tap((filters) => {
const reducer = (acc: Params, curr: [string, string[]]) => {
const [key, values] = curr;
return { ...acc, [key]: values.join(',') };
};
const queryParams = Object.entries(filters).reduce(reducer, {});
this.router.navigate([], {
relativeTo: this.route,
queryParams,
queryParamsHandling: 'merge',
});
})
);
merge(updateStateFromRoute$, updateRouteFromState$)
.pipe(takeUntilDestroyed())
.subscribe();
}
This discussion aims to explore whether there is a real need for this feature and to collaboratively define its scope and specifications. After reaching consensus during this initial phase (if there is a need), I'm eager to take the next step and bring this feature to life, realizing my aspiration to contribute meaningfully to the Angular ecosystem. 🤩
Your thoughts and suggestions on this proposal would be highly appreciated!
Hi,
I tried to do something like this, automatic synchronization of queryParams, but the issues is:
There are cases where we want to keep some queryParams, and cases when we want to merge, or remove or do a lot of other things, and that's why navigate method of the router includes a lot of other keys in there that we would need to support or duplicate.
That's why there's nothing in ngxtension that simplifies this.
It's a pity, because I think that for simple and common cases as described above it would have been practical, but I note that it's not generic enough to be included in this repo ;)