I want to load distinct components for similar structured paths.
Means:
example.com/whatever-post-slug --> Load Post Component
example.com/whatever-page-slug --> Load Page Component
example.com/hello-i-am-a-string --> Load Post Component (because that slug belongs to a post)
example.com/about-us --> Load Page Component (because that slug belongs to a page)
Why would I do this?
One may think: hey but why don't you just define two prefixed different paths, which is what paths are there for?
The answer is: I am creating (by the way an open source) Angular WordPress theme and I want the routing to be something the user decides, without forcing any hardcoded structure.
In one of the multiple possibilities, Posts and Pages (if you don't know WordPress, just know those are two distinct content types which I want to use independent Modules and Components for) may share slugs at a root level.
And at a first glance, I cannot know if the slug belongs to a post or a page or something else (so UrlMatcher
cannot help here).
Did I think of a solution?
Yes, of three.
First solution
I could create a Wrapper Component that will load for the catchall route, and then, inside this component do something like:
<whatever-a-component *ngIf="showComponentA()"><whatever-a-component>
<whatever-b-component *ngIf="showComponentB()"></whatever-b-component>
And let the Wrapper Component do all the logic.
This adds an extra intermediate Component into the game.
Second solution
Use a resolver for the catchall and then do all the logic in the resolver.
The problem is, I need to subscribe to an http communication to know what kind of content type I am dealing with, and since the Resolver resolve() method needs to return an Observable, it's not nice to subscribe in there.
Of course, it works, if I return a placeholder observable that waits for a while, like so:
// ... inside resolve()
// Force an observable return to allow suscriptions take their time
return Observable.create(observer => {
setTimeout(() => {
console.log("Resolver observable is done");
observer.complete();
}, 15000);
});
... or if I pipe my subscription with mergeMap()
and return EMPTY when I get the results from my subscription.
And once I get my data back I can set new routes including the current path that must point to its specific component.
That does not seem a very clean approach to me.
Third solution
Just load a normal Dispatcher Component that will do all the checkings at OnInit()
and then navigate to a "secret" component-specific url but using { skipLocationChange: true }
, so the user will have the correct route and also the correct component will be loaded.
But again, this adds an extra intermediate component into the game.
I think this is the cleanest solution, since at the App Routing module I can do something like:
{
path: 'wp-angular/view-post/:id',
loadChildren: () => import('./view-post/view-post.module').then(m => m.ViewPostModule)
},
{
path: 'wp-angular/view-page/:id',
loadChildren: () => import('./view-page/view-page.module').then(m => m.ViewPageModule)
}
So, I will have those modules lazy loaded only if the user actually visits one of those two content types.
Also, that content type component will already be available if the user then visits a second content of the same type.
And the fact that I can use { skipLocationChange: true }
will make it possible to keep the path as expected.
Plus, this allows displaying navigation loading feedback without the need to subscribe to the Router Events.
Question
What would you do and why?
Maybe I am missing some magic Angular feature that allows doing this in a straight-to-the-point way.