Search code examples
angularlazy-loadingangular-routerangular10

Angular router: named outlets does not seem to work with relative routes nor in lazy loaded modules


I have some problems with the angular router and named outlets. I have a list of members, and want to edit a clicked member on the same page in a named router outlet. And I would like to have the member handling in a lazy loaded module.

StackBlitz

It works when the module is not lazy loaded. The template looks like this:

<tr *ngFor="let member of members">
...
<a [routerLink]="['/home/member', {outlets: {right: ['memberedit', member._id]}}]"> Edit</a>

And the routes array:

const routes: Routes = [
  {
    path: 'home', component: HomeComponent,
    children: [
      {
        path: 'member', component: MemberComponent,
        children: [
          {
            path: 'memberlist', component: MemberListComponent,
            resolve: { resolvedListData: MemberListResolver },
          },
          {
            path: 'memberedit/:id', component: MemberEditComponent,
            resolve: { resolvedMember: MemberResolver }
            outlet: 'right',
          },
        ]
      },
    ]
  },
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent }
];

When I try to remove he reference to /home/member in order to prepare for lazy loading, it no longer works:

I have tried with:

<a [routerLink]="[{outlets: {right: ['memberedit', member._id]}}]">Edit</a>

This doesn't work, I think this is a known error, the router constructs this URL with a single slash after memberlist:

/home/member/memberlist/(right:memberedit/5f39748b88457a30e32e909d)

And I have tried with:

<a [routerLink]="['../', {outlets: {right: ['memberedit', member._id]}}]"> Edit</a>

It works the first time I click on a member. And gives this URL:

/home/member/(memberlist//right:memberedit/5f39748b88457a30e32e909d)

The second time I click on another member I get this error:

Error: Two segments cannot have the same outlet name: 'memberedit/5f4227887687e3162d94f5a3' and 'memberedit/5f39748b88457a30e32e909d'.

And the URL is:

/home/member/(/(right:memberedit/5f4227887687e3162…f5a3)//right:memberedit/5f39748b88457a30e32e909d)"

I have also tried with:

<a [routerLink]="[{outlets: {primary: ['memberlist'], right: ['memberedit', member._id]}}]">Edit</a>
<a [routerLink]="['../', {outlets: {primary: ['memberlist'], right: ['memberedit', member._id]}}]">Edit</a>

Without any luck.

I have tried with "full pathname" in the lazy loaded module that doesn't work.

<a [routerLink]="['/home/member', {outlets: {right: ['memberedit', member._id]}}]"> Edit</a>

Gives:

url: "/home/member/(memberlist//right:memberedit/5f39748b88457a30e32e909d)", urlAfterRedirects: "/home/member"

And I have tried all the above in the lazy loaded module. Any ideas?


Solution

  • It should work with the lazy-loaded module this way:

    lazy-member-routing.module

    const routes: Routes = [
        // {
        //     path: '', component: LazyMemberComponent,
        //     children: [
                {
                    path: 'list', component: ListComponent,
                },
                {
                    path: 'edit/:id', component: EditComponent,
                    outlet: 'lazyright',
                },
        //     ]
        // },
    ];
    

    app-routing.module.ts

    const routes: Routes = [
      {
        path: 'home', component: HomeComponent,
        children: [
          {
            path: 'member', component: MemberComponent,
            children: [
              {
                path: 'list', component: ListComponent,
              },
              {
                path: 'edit/:id', component: EditComponent,
                outlet: 'right'
              }
            ]
          },
          { 
            path: 'lazymember', 
            loadChildren: () => import('./lazy-member/lazy-member.module').then(m => m.LazyMemberModule),
            component: LazyMemberComponent,
          }
        ]
      },
      { path: '', redirectTo: 'home', pathMatch: 'full'}
    ];
    

    list.component.html

    <a [routerLink]="['/home/lazymember', {outlets: {lazyright: ['edit', member.id]}}]">Edit works - No, no longer</a>
    

    StackBlitz demo


    It did not work initially with path: '', component: LazyMemberComponent, because of how Angular Router resolves navigations. Basically, for each child of an UrlSegmentGroup, it will loop through the current routes array and will try to find a match.

    For example, the main UrlSegmentGroup would look like this:

    {
       children: {
         primary: {
          children: {
             lazyright: {
                children: {},
                segments: ['edit', '2']
             },
             primary: {
                children: {},
                segments: ['list'],
             }
          }
          segments: ['home', 'lazymember']
         } 
       },
       segments: []
    }
    

    Then,

    • 'home' segment will match path: 'home', component: HomeComponent,
    • 'lazymember' segment will match path: 'lazymember' ...
    • now we have lazyright and primary, both of which must find their match in the current routes array, which is now [{ path: '', component: LazyMemberComponent, children: [...] }]; this is the reason it did not work before

    If you'd like to read a bit more about UrlTrees and UrlSegmentGroups, I've written an article on this.