Zac
Zac

Reputation: 828

Ionic/Angular nested router-outlet: routerLinks only work once

I'm trying to implement a simple example of nested/child routes in an Angular Ionic v5 app, using Angular's routing guide. I created a project using ionic start and selected the blank template, then created two components ChildAComponent and ChildBComponent. Here's the relevant code snippet from home.page.html:

<div id="container">
  <strong>Ready to create an app?</strong>
  <p>Start with Ionic <a target="_blank" rel="noopener noreferrer"
      href="https://ionicframework.com/docs/components">UI Components</a></p>
  <div id="stuff">
    <a routerLink="child-a">Child A</a><br>
    <a routerLink="child-b">Child B</a><br>
    <a routerLink="404">404</a>
  </div>
  <div id="thing">
    <router-outlet></router-outlet>
  </div>
</div>

I'm trying to use the a tags to drive what component renders in the router-outlet. When I first load the page (URL: /home) I can click one of the links and the appropriate child element renders in the outlet. When this happens, though, the hrefs on the a tags (which I'm assuming are put there by Angular based on the routerLinks) all change to the navigated-to URL.

As such, I can't click on the other component link to render that component without reloading the page (going back to /home).

Before clicking a link (URL is /home):

href's correct

After clicking Child A link (URL is /home/childa):

enter image description here

The Angular docs don't seem to indicate I've configured anything incorrectly, and I can't see anything glaringly different in my code compared to the routing in the heroes example. What am I doing wrong?

Here's my home-routing.module.ts if it helps:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomePage } from './home.page';
import { ChildAComponent } from './childa/childa.component';
import { ChildBComponent } from './childb/childb.component';

const routes: Routes = [
  {
    path: '',
    component: HomePage,
    children: [
      {
        path: 'child-a',
        component: ChildAComponent,
      },
      {
        path: 'child-b',
        component: ChildBComponent,
      },
    ]
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class HomePageRoutingModule {}

GitHub: https://github.com/zrev2220/router-link-bug

Upvotes: 5

Views: 2240

Answers (1)

Andrei Gătej
Andrei Gătej

Reputation: 11979

Short answer

You'd have to add the absolute path to your RouterLink directives:

<a [routerLink]="['/home', 'child-a']">Child A</a><br>
<a [routerLink]="['/home', 'child-b']">Child B</a><br>

Detailed answer

Let's first understand how the RouterLink(specifically RouterLinkWithRef) directive works. The route it will navigate to depends on the current activated route(ActivatedRoute). This is what happens when you click on an element with RouterLinkWithRef directive(e.g selector: 'a[routerLink]'):

/* ... */
this.router.navigateByUrl(this.urlTree, extras);
/* ... */

where this.urlTree is defined as a getter:

get urlTree(): UrlTree {
  return this.router.createUrlTree(this.commands, {
    relativeTo: this.route, // !
    queryParams: this.queryParams,
    fragment: this.fragment,
    preserveQueryParams: attrBoolValue(this.preserve),
    queryParamsHandling: this.queryParamsHandling,
    preserveFragment: attrBoolValue(this.preserveFragment),
  });
}

as we can see, there is relativeTo: this.route, where this.route is injected as ActivatedRoute.

This is why the problem occurred: when the current route is /home, the injected activated route in the directive should be different than the one from /home/child-*; in this case, it's the same.

So, when the user first lands on /home, the ActivatedRoute injected in the directive will have a certain value, let's call it X. But when the user goes to /home/child-a, the view outside router-outlet remains the same, and so does the ActivatedRoute's value. This is important because when dealing with absolute and relative route paths(anything provided to router directives which doesn't have a / prepended to it), Angular will traverse the current UrlTree(UrlTree = the deserialized version of a route path provided as a string) and will try to replace the old part with the new one.
After the user navigates to /home/child-a, then in the current UrlTree there is no way to find an old part created from the current ActivatedRoute in the directive, because ActivatedRoute no longer corresponds with the actual activated route, so it's outdated.

Upvotes: 5

Related Questions