Marcus Brunsten
Marcus Brunsten

Reputation: 572

Using child routing in the same router outlet as parent(s)

I want to be able to use the same router-outlet for some routes.

Routing setup (simplified):

export const routes: Routes = [
    {
        path: 'app', component: AppComponent,
            children: [
                path: 'category/:id', component: CategoryComponent,
                children: [
                    { path: 'post/:id', component: PostComponent }
                ]
            ]
    }
];

For example we have this path:

/app/category/1/post/1

That breaks into

/app - AppComponent
|_ /catory/1 - CategoryComponent
   |_/post/1 - PostComponent

The AppComponent has a <router-outlet> which renders CategoryComponent, but should also render the PostComponent when that route is active.

Common answers for this type of question:

Move the child routes and add them in the app-route children array

No. This isn't the right way. We still want our hierarchy of routes. CategoryComponent may know something which PostComponent doesn't - Such as Breadcrumb naming

So we still want our CategoryComponent to load. (Even if it's view isn't renders)

Use a <router-outlet> inside of CategoryComponent

No. The CategoryComponent should not be in charge of it's own <router-outlet>. The PostComponent should be rendered in place of the CategoryComponent, and add CSS to place it like that should be illegal.

How can I acheive this behaviour?

Do i need to write my own router-outlet?

Will this be solved in Angular4?

Any tips are welcome! Thanks

Upvotes: 10

Views: 5579

Answers (3)

cjbarth
cjbarth

Reputation: 4469

The answer here will work.

Depending on how you're building breadcrumbs, you may have to have several nested levels of children: [] with path: ''.

  • The children will render in the first component they find in their chain of parents.
  • The router will build a path out of all the path properties, which if there are '', will just walk up to use the parents path property.
  • The breadcrumb builder will also walk up the chain of children, but if it finds data: {breadcrumb: 'crumb'} or data: {title: 'crumb'} or whatever it is looking for, it will build that into the breadcrumb chain.

Thus, you can make breadcrumbs as long as needed, and have paths be as long as needed, but as long as you don't nest components, you'll get what you're after.

export const routes: Routes = [
  {
    path: 'app',
    component: AppComponent,
    data: {
      title: 'App',
    },
    children: [
      {
        path: 'category/:id',  // appends to path `app`
        children: [
          {
            path: '',
            data: {
              title: 'Category',  // appends to breadcrumb `App`
            },
            children: [
              {
                path: '',
                component: CategoryComponent,  // renders in `AppComponent`
                data: {
                  title: '',  // Specify a blank on so that we don't duplicate the parent one
                },
              },
              {
                path: 'post/:id',  // appends to path `app/category/:id`
                component: PostComponent,  // renders in `AppComponent`
                data: {
                  title: 'Post',  // appends to breadcrumb `App / Category`
                },
              },
            ],
          },
        ],
      },
    ],
  },
];

Upvotes: 2

Robert
Robert

Reputation: 1940

Since we're meanwhile at Angular 7 and there seems to be no documentation for this, I guess that there is no simple out-of-the box solution for the problem.

I had a very similar problem: The child route of a lazy loaded module should be rendered inside the app component's outlet and not inside an outlet of the lazy loaded module. I'm working with lazy loaded modules but you probably can transform this idea to your use case. I was able to solve the issue by defining the according route on the expected outlet level, meaning inside the AppRoutes (I think there is no other way):

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'category/:cid',
    loadChildren: './category/category.module#CategoryModule',
  },
  {
    path: 'category/:cid/post/:pid',
    loadChildren: './category/post/post.module#PostModule'
  },
];

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

This way I can at least guarantee the folder structure and URL hierarchy remain the way I want them to be. In order to solve the state management problem you mentioned, I would deserialize data about categories inside the PostComponent or PostRoute. However, this would introduce some maybe unwanted redundancy.

To me this is far from ideal, because I'm not able to encapsulate things inside lazy loaded modules the way I want. But: it's working.

Upvotes: 1

Doug
Doug

Reputation: 195

If I get it right, you can try to solve the problem using something like "wrappers". This way (I have not tested this code, but it should work; read the inline comments):

export const routes: Routes = [
    {
        path: 'app',
        component: AppComponent,
        children: [
            {
                path: 'category/:id', // app/category/:id/
                children: [
                    {
                        path: '', // app/category/:id/ => By default, empty paths serve as if they were the parent path.
                        component: CategoryComponent,
                        children: [ // These children will be inside the <router-outlet> of the CategoryComponent.
                            {
                                path: 'subcategory1', // app/category/:id/subcategory1
                                component: PostComponent,
                            },
                            {
                                path: 'subcategory2', // app/category/:id/subcategory2
                                component: PostComponent,
                            }
                        ]
                    },
                    { // The PostComponent will use AppComponent as its parent; will be loading inside the <router-outlet> of AppComponent.
                        path: 'post/:id', // app/category/:id/post/:id
                        component: PostComponent
                    }
                ]
            }
        ],
    },
];

Upvotes: 4

Related Questions