Skip to content
corpus.web

Angular Router — Feature Modules, Child Routes, and Services

Child routes and per-feature route files keep a growing app's routing config from collapsing into one flat list at the root

Baseline Angular 22.1.1
Kind Concept
View source

Building on Angular Router, today we'll look at feature modules, child routes, redirects, routing modules, and key router services.

Feature modules

With the Day 27 sample app, can we split the app into multiple NgModules instead of one monolithic module — and still use the Router? Yes: use RouterModule.forChild in feature modules.

Extract ArticleModule

Create a module and move related declarations into it:

ts
import { NgModule } from '@angular/core';import { CommonModule } from '@angular/common';import { ArticleListComponent } from './article-list/article-list.component';import { ArticleDetailComponent } from './article-detail/article-detail.component';
@NgModule({  imports: [CommonModule],  declarations: [ArticleListComponent, ArticleDetailComponent],})export class ArticleModule {}

Configure routes with forChild instead of forRoot (see routing intro for why):

ts
import { NgModule } from '@angular/core';import { CommonModule } from '@angular/common';import { Routes, RouterModule } from '@angular/router';import { ArticleListComponent } from './article-list/article-list.component';import { ArticleDetailComponent } from './article-detail/article-detail.component';
const routes: Routes = [  {    path: 'article',    component: ArticleListComponent,  },  {    path: 'article/:slug',    component: ArticleDetailComponent,  },];
@NgModule({  imports: [CommonModule, RouterModule.forChild(routes)],  declarations: [ArticleListComponent, ArticleDetailComponent],})export class ArticleModule {}

Import the feature module in AppModule:

ts
import { ArticleModule } from './article/article.module';
@NgModule({  imports: [    BrowserModule,    FormsModule,    ArticleModule, // note import order    AppRoutingModule,  ],  declarations: [AppComponent],  bootstrap: [AppComponent],})export class AppModule {}

Navigate to article to see the list.

App Feature Route
App Feature Route

Route redirects

Redirect one path to another:

ts
const routes: Routes = [  {    path: '',    redirectTo: 'article',    pathMatch: 'full',  },];

For redirects, pathMatch: 'full' is usually what you want.

pathMatch strategies

  • full — the entire URL path must match (like ==). User wants tiepphan.com/abc/xyz → path abc/xyz; abc/cde does not match.
  • prefix (default) — matching prefix is enough. For tiepphan.com/abc/xyz, path abc matches.

Sample: https://stackblitz.com/edit/angular-100-days-of-code-day-28-router-feature-1?file=src%2Fapp%2Farticle%2Farticle.module.ts

Routing module pattern

Split routing into its own module — like AppRoutingModule. For a feature:

ts
const routes: Routes = [  {    path: 'article',    component: ArticleListComponent,  },  {    path: 'article/:slug',    component: ArticleDetailComponent,  },];
@NgModule({  imports: [    CommonModule,    RouterModule.forChild(routes),  ],  declarations: [],  exports: [RouterModule],})export class ArticleRoutingModule {}

ArticleModule imports ArticleRoutingModule instead of calling RouterModule.forChild directly:

ts
import { ArticleRoutingModule } from './article-routing.module';
@NgModule({  imports: [CommonModule, ArticleRoutingModule],  declarations: [ArticleListComponent, ArticleDetailComponent],})export class ArticleModule {}

Full code: https://stackblitz.com/edit/angular-100-days-of-code-day-28-router-feature-2?file=src%2Fapp%2Farticle%2Farticle-routing.module.ts

Child routes

These routes share a prefix:

Flat style

ts
const routes: Routes = [  {    path: 'article',    component: ArticleListComponent,  },  {    path: 'article/:slug',    component: ArticleDetailComponent,  },];

Parent–child style (equivalent):

ts
const routes: Routes = [  {    path: 'article',    children: [      {        path: '',        component: ArticleListComponent,      },      {        path: ':slug',        component: ArticleDetailComponent,      },    ],  },];

A parent route can also activate a layout component that contains a router-outlet for children:

ts
const routes: Routes = [  {    path: 'article',    component: ArticleComponent, // layout component    children: [      {        path: '',        component: ArticleListComponent,      },      {        path: ':slug',        component: ArticleDetailComponent,      },    ],  },];

Full code: https://stackblitz.com/edit/angular-100-days-of-code-day-28-router-feature-3?file=src%2Fapp%2Farticle%2Farticle-routing.module.ts

ActivatedRoute service

Provides access to information about a route associated with a component that is loaded in an outlet. ActivatedRoute

Use it to read params, query strings, and route data.

Retrieve params

From routing.md — snapshot approach:

ts
export class ArticleDetailComponent implements OnInit {  article$: Observable<Article>;  constructor(private _route: ActivatedRoute, private _api: ArticleService) {}
  ngOnInit(): void {    let slug = this._route.snapshot.paramMap.get('slug');    this.article$ = this._api.getArticleBySlug(slug);  }}

Observable approach (better when the same component instance is reused):

ts
export class ArticleDetailComponent implements OnInit {  article$: Observable<Article>;  constructor(private _route: ActivatedRoute, private _api: ArticleService) {}
  ngOnInit(): void {    this.article$ = this._route.paramMap.pipe(      map((params) => params.get('slug')),      switchMap((slug) => this._api.getArticleBySlug(slug))    );  }}

New to RxJS? See the RxJS articles.

Why Observable instead of snapshot?

By default the router tries to reuse a component when configuration matches. Navigating from /article to /article/bai-viet-1 creates a new ArticleDetailComponent — snapshot and paramMap agree.

Navigating from /article/bai-viet-1 to /article/bai-viet-2 reuses the same instance. Snapshot is frozen at creation time; paramMap emits the new slug.

Choose based on whether params can change without recreating the component.

paramMap Observable
paramMap Observable

paramMap snapshot
paramMap snapshot

Samples:

Query params, route data, and more

Besides paramMap, use queryParamMap for query strings. For URL tiepphan.com/page/2?sort=createdDate:

ts
this._route.snapshot.queryParamMap.get('sort');

Or observe:

ts
queryParamMap.subscribe((query) => {  console.log(query.get('sort'));});

Route data and other APIs are documented on ActivatedRoute.

Router service

A service that provides navigation and URL manipulation capabilities. Router

Navigate programmatically after an action succeeds:

ts
navigateByUrl(url: string | UrlTree, extras: NavigationExtras = { skipLocationChange: false }): Promise<boolean>;navigate(commands: any[], extras: NavigationExtras = { skipLocationChange: false }): Promise<boolean>;
ts
class SomeComponent {  constructor(private router: Router) {}  onClick() {    // do something    this.router.navigate(['/article']);  }}

Listen to navigation events:

ts
this.router.events  .pipe(filter((e) => e instanceof NavigationEnd))  .subscribe((e) => {    console.log(e);  });

Summary

Feature modules with forChild, redirects, routing modules, child routes, ActivatedRoute, and Router are essential for real Angular apps. Read the official docs and router source when you need deeper detail.

References

0%