Today I want to show you a possibility to navigate to external URLs with dynamic parameters. I’ve used something like this in my previous and also in my current project, so I thought to blog about (kudos for my colleagues). The core idea is to have dedicated routes with route parameters in the usual routing module and then use a guard to handle the parameter replacement and redirection. To provide the replacement parameters dynamically during navigation, the solution will use the state
property of the router NavigationExtras, which comes in handy for us here.
Let’s show the code first:
@Injectable() export class ExternalRouteGuard implements CanActivate, CanActivateChild { constructor(private readonly router: Router) {} canActivate(route: ActivatedRouteSnapshot): boolean { let url: string = route.data.url || route.params.url; // append dynamic query params if (route.queryParams) { const queryString = Object.entries(route.queryParams) .map(entry => entry.join('=')) .join('&'); if (queryString) { url += `?${queryString}`; } } // evaluate and replace dynamic route parameters (:param) const replacements = this.router.getCurrentNavigation().extras.state || {}; if (replacements) { const replaceRegex = /:(.*?)(\/|\?|$)/g; let replaceMatch = replaceRegex.exec(url); while (replaceMatch != null) { const replaceParam = replaceMatch[1]; const replacement = replacements[replaceParam]; if (replacement) { url = url.replace(`:${replaceParam}`, replacement); } replaceMatch = replaceRegex.exec(url); } } // navigate to constructed URL const windowTarget: string = route.data.target || '_self'; window.open(url, windowTarget); return false; } canActivateChild(route: ActivatedRouteSnapshot): boolean { return this.canActivate(route); } }
First, we determine the URL to which we want to navigate. This can be set statically in the data
of the route definition or provided dynamically inside the route params. Second, we append query params which are provided dynamically with the router.navigate()
call.
Next, we replace the URL params provided in a :param
form in the URL. We retrieve the actual values from the router state using router.getCurrentNavigation().extras.state
. Then we look up all parameters which want to be replaced and replace them (if a replacement value was provided).
In the last step, we navigate to the final URL using window.open()
.
Using this guard is very easy: just place it in your routing-module for the routes you want to navigate to externally. Note that Angular needs a component reference in order to get a working navigation. We can just add an „empty“ component for this case. I’ve found it convenient to have a separate „external“ parent route with the guard and then child routes for every concrete external URL:
... @Component({template: ''}) export class EmptyComponent { const routes = [ ... { path: 'external', canActivateChild: [ExternalRouteGuard], children: [ { path: 'another-frontend', component: EmptyComponent, data: { url: '/another-frontend/user/:userId' target: '_blank' } }, { path: 'www' } ] }] as Routes; @NgModule({ declarations: [ EmptyComponent ], ... }) export class AppRoutingModule { }
Now you can navigate to those URLs very easily:
router.navigate(['external','another-frontend'], {state: {userId: 123}}); router.navigate(['external','another-frontend'], { state: {userId: 123}, queryParams: {q: 'anything'} }); ... router.navigate(['external', 'www', {url: 'https://www.any-public.url'}]);
2 Kommentare
-
Hi!
I am trying to use this solution but the framework is returning that component, redirectTo, children or loadChildren MUST be provided for ‚external/www‘. I don’t think this solution works on the current version of angular.
-
Autor
Hi Gustavo.
Thanks for your finding!
We are using this solution with Angular 11, but our setup is different: we have our guards in a submodule, thus your error doesn’t occur for us. In your case, just add an empty component („@Component({template: “}) export class EmptyComponent {}“) to your external routes, this will work. I will update the article accordingly.