0
votes

I have a difficult time understanding the exact algorithm angular router uses, specifically for auxiliary routes.

What I've done

I've read docs where didn't find any clue about how the route matching works, especially for auxiliary routes (maybe I just didn't find, but I tried very hard), but found question that explained the process for regular routes.

I've googled and solved a few related problems. More specifically, I had a problem with a parent who has blank path, which doesn't allow some auxiliary routes work properly, see this and that

Example

The example is derived from my project. Suppose I have two main modules in my app (and 2 routing modules, 4 in total).

app.module.ts

...

@NgModule({
  declarations: [
    AppComponent,
    NotFoundComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    AdminModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app-routing.component.ts

...

const routes: Routes = [
    {path: '**', component: NotFoundComponent}
];

@NgModule({
    imports: [AdminModule, RouterModule.forRoot(routes, {enableTracing: true})],
    exports: [RouterModule]
})
export class AppRoutingModule {
}

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>\n',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'Results System';
}

admin-routing.module.ts

...

const routes: Routes = [
    {
        path: 'dashboard', component: DashboardComponent, children: [
            { path: 'competitions', component: CompetitionsListComponent},
            { path: '', component: DashboardNavComponent, outlet: 'sidebar'},
            { path: 'create', component: CreateComponent, outlet: 'details'},
        ]
    },
    { path: '', pathMatch: 'full', redirectTo: 'dashboard'}
];

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

admin.module.ts - excluded, nothing special, just import admin-routing.module.ts module

dashboard.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  template: `
    <router-outlet name="sidebar"></router-outlet>
    <router-outlet name=""></router-outlet>
    <router-outlet name="details"></router-outlet>
  `,
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
  constructor() { }

  ngOnInit(): void {
  }
}

Results

  • localhost:4200/dashboard/competitions two children components (CompetitionsListComponent, DashboardNavComponent) are rendered as expected

  • localhost:4200/dashboard/competitions(details:create) link won't render all three children components (CompetitionsListComponent, DashboardNavComponent, CreateComponent) and just throw an error:

NavigationError(id: 1, url: '/dashboard/competitions(details:create)', error: Error: Cannot match any routes. URL Segment: 'create')

  • localhost:4200/dashboard/competitions(details:create) same

NavigationError(id: 1, url: '/dashboard/competitions(details:dashboard/create)', error: Error: Cannot match any routes. URL Segment: 'dashboard/create'

What I don't understand

  • How exactly a child module adds its routing configs to the root configs? Does lazy loading affects this in any way? Is it just appended to the object like that?:
[
    // children module routes
    {
        path: 'dashboard', component: DashboardComponent, children: [
            { path: 'competitions', component: CompetitionsListComponent},
            { path: '', component: DashboardNavComponent, outlet: 'sidebar'},
            { path: 'create', component: CreateComponent, outlet: 'details'},
        ]
    },
    { path: '', pathMatch: 'full', redirectTo: 'dashboard'}

    ...other children module routes...


    // root module routes
    {path: '**', component: NotFoundComponent},
];
  • Why NotFoundComponent doesn't work in the case (when error appears), doesn't it cover every path, or auxiliary routes have to have this pattern for every outlet like that:
    {path: '**', component: NotFoundComponent, outlet: 'sidebar'}
    {path: '**', component: NotFoundComponent, outlet: 'details'}
  • How does an auxiliary route find a named outlet it'll use? Does it search for the first parent with the appropriate outlet and if don't find, throws an error (excluding a case from the point above)?

  • And in the end why localhost:4200/dashboard/competitions(details:create) isn't resolved to DashboardComponent with CreateComponent inside. Do I need to specify url for auxiliary route another way? More specifically, how are auxiliary paths resolved in the context when they are children of other non-auxiliary route components?

1

1 Answers

0
votes

After additional researches (this time on Reddit) I finally found the answers, here the article (you can skip everything before Making the Side Menu adjust to the current Url but read after). The following answers may not be 100% right, as I got them through experimentation.

How exactly a child module adds its routing configs to the root configs? Does lazy loading affects this in any way? Is it just appended to the object like that?:

[
    // children module routes
    {
        path: 'dashboard', component: DashboardComponent, children: [
            { path: 'competitions', component: CompetitionsListComponent},
            { path: '', component: DashboardNavComponent, outlet: 'sidebar'},
            { path: 'create', component: CreateComponent, outlet: 'details'},
        ]
    },
    { path: '', pathMatch: 'full', redirectTo: 'dashboard'}

    ...other children module routes...


    // root module routes
    {path: '**', component: NotFoundComponent},
];

Yes, it works the way I described, it adds children routes to the beginning of the root config array. No, lazy-load doesn't affect this in any way, it just loads a module in the route you specified. How did I check? I loaded Router as dependency to one of my components and then upon a button click logged config variable of Router instance

Why NotFoundComponent doesn't work in the case (when error appears), doesn't it cover every path, or auxiliary routes have to have this pattern for every outlet like that:

    {path: '**', component: NotFoundComponent, outlet: 'sidebar'}
    {path: '**', component: NotFoundComponent, outlet: 'details'}

And Yes and No, in my scenario with the following path localhost:4200/dashboard/competitions(details:create) this works and prevents the error being thrown (if you specify the pattern on the same level as auxiliary component, not in the root), but when I change segment to the right one with a wrong auxiliary path (see the last point) localhost:4200/dashboard/(competitions//wrong:aux-path) one primary pattern in the root handles this. If anyone can clarify this, I'll highly appreciate

How does an auxiliary route find a named outlet it'll use? Does it search for the first parent with the appropriate outlet and if don't find, throws an error (excluding a case from the point above)?

As I understood, No, it uses the parent above like with regular (primary) routes and like vice if it doesn't find suitable outlet in the direct parent, it just isn't rendered. Broader explanation you can find in the last point.

And in the end why localhost:4200/dashboard/competitions(details:create) isn't resolved to DashboardComponent with CreateComponent inside. Do I need to specify url for auxiliary route another way? More specifically, how are auxiliary paths resolved in the context when they are children of other non-auxiliary route components?

From the article I referenced before I got the following explanation:

What does a multiple outlet URL look like? By triggering the programmatic navigation call above, the browser will display the following URL: /courses/(development//sidemenu:development)

This URL means:

  • the courses URL segment is active
  • inside it, the primary route is set to /courses/development
  • the auxiliary child route 'development' is active for the outlet sidemenu

So, in my case I need to use localhost:4200/dashboard(competitions//details:create) where:

  • /dashboard - active segment
  • (competitions//details:create) - multiple outlets for the segment delimited by //
  • competitions - primary outlet route
  • details:create - auxiliary outlet route