A question asked under the micro-learning video Custom Angular URL Matchers in Spartacus Storefront
Can we take prefix data from API and based API response have to decide whether matcher is true or not. Is that possible?
(here are the Spartacus docs on the Custom Angular URL Matchers)
The question is not about Spartacus, but more about the native Angular URL Matchers.
By design, the URL Matchers in Angular are synchronous. Therefore they cannot wait for any backend API response that is asynchronous.
I'd be happy to understand the usecase for async Angular URL matcher. That said, you can achieve a somewhat similar result with a workaround.
You can match an Angular Route anyway (e.g. with the route path **
or other path or matcher), and then apply an Angular Guard to it, which can wait for async data from the backend API, and based on the response, decide whether to redirect to a different route.
But if you really need to have an Angular URL Matcher that is driven by some backend data, you can configure the URL Matcher upfront, before the whole application and route application starts, in the Angular APP_INITIALIZER phase.
Caveat: your custom async logic in APP_INITIALIZER
by design will be blocking the app from starting, until you're done with your async logic (e.g. until you'll load your data from backend API). Therefore it may slow down the start the whole app for your users. So I would not recommend this solution.
How to:
For example, you can provide a custom APP_INITIALIZER
that can wait for your async data from backend before bootstrapping the rest of the application. Especially in your custom APP_INITIALIZER
you can inject the Angular Router
and mutate its existing (or add new) Angular Routes
objects in runtime , but before the app is bootstrapped and before those Routes objects are used to match the current URL. Please note that this technique is pretty unconventional! (normally, in tutorials they define the Angular Routes
objects statically, e.g. RouterModule.forChild({ ... })
)
e.g. Below you can find an example of a custom APP_INITIALIZER
that loads data from backend, and then creates a new Route object in runtime, with a custom URL matcher. Still, I wouldn't recommend it, unless you have a really strong use case for it.
export function createRouteInRuntime(injector: Injector) {
return () => {
// Router needs to be injected lazily via `Injector` to avoid possible cyclic dependency with `APP_INITIALIZER`:
const router = Injector.get(Router);
const httpClient = Injector.get(HttpClient)
return httpClient.get('some/api').pipe(
tap((apiResponse) => {
const newRoute: Route = {
component: /* ... */
matcher: /* ... here your custom matcher using apiResponse data ... */
};
// add a new Route object to the array in the Angular Router:
router.resetConfig([...router.config, newRoute]);
})
)
}
}
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
deps: [Injector],
multi: true,
useFactory: createRouteInRuntime
},
])
export AppModule {}