Skip to content
Angular ng routing 4 min read

RouterOutlet & RouterLink

Once your routes are configured, two template directives bring them to life: RouterOutlet marks the spot where the matched component should render, and RouterLink turns ordinary elements into declarative navigation triggers. Together they let Angular swap views in place without a full page reload, preserving application state and giving users instant transitions. Understanding how the outlet hosts components and how links resolve relative to the active route is fundamental to building any Angular app with more than one screen.

Placing a router-outlet

A <router-outlet> is a placeholder element. Angular renders the component for the currently matched route as a sibling immediately after the outlet element in the DOM. Everything outside the outlet — your header, navigation, footer — stays mounted while the routed content changes.

Because everything is standalone in modern Angular, you import RouterOutlet directly into the component that owns the layout:

import { Component } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, RouterLink, RouterLinkActive],
  template: `
    <header>
      <nav>
        <a routerLink="/" routerLinkActive="active"
           [routerLinkActiveOptions]="{ exact: true }">Home</a>
        <a routerLink="/products" routerLinkActive="active">Products</a>
        <a routerLink="/about" routerLinkActive="active">About</a>
      </nav>
    </header>

    <main>
      <router-outlet />
    </main>
  `,
  styles: `.active { font-weight: 700; text-decoration: underline; }`,
})
export class AppComponent {}

The outlet content is whatever component your route config maps to the current URL. You only need one outlet per “level” of routing — nested routes get their own outlets inside their parent component, covered in the child routes page.

A <router-outlet> does not place the routed component inside itself; the component is inserted after it. Styling the outlet element directly has no visible effect — style the routed components or a wrapping element instead.

Never use a plain href for in-app navigation — it triggers a full document reload and destroys application state. Use the routerLink directive, which intercepts the click and asks the router to navigate.

routerLink accepts either a string or an array (a “link parameters array”). The array form is preferred when you build URLs from dynamic segments, because each element is treated as a path part and properly encoded:

<!-- String form -->
<a routerLink="/products">All products</a>

<!-- Array form with a dynamic segment -->
<a [routerLink]="['/products', product.id]">View {{ product.name }}</a>

<!-- With query params and a fragment -->
<a [routerLink]="['/products']"
   [queryParams]="{ sort: 'price', page: 2 }"
   fragment="results">
  Sorted products
</a>

The second example resolves to /products/42, and the third to /products?sort=price&page=2#results.

A link beginning with / is absolute and resolves from the application root. A link without a leading slash is relative to the current activated route. Relative links keep nested components decoupled from their absolute position in the route tree.

<!-- Absolute: always /settings/profile -->
<a routerLink="/settings/profile">Profile</a>

<!-- Relative: appended to the current route's path -->
<a routerLink="profile">Profile</a>

<!-- Navigate up one level then into 'billing' -->
<a [routerLink]="['../billing']">Billing</a>

routerLinkActive adds one or more CSS classes to an element while its routerLink matches the current URL. This is how you build navigation that highlights the current page.

<a routerLink="/products" routerLinkActive="active strong">Products</a>

By default the match is prefix-based: a link to /products stays active on /products/42 too. For the home link / this is a problem — it would match every URL. Pass routerLinkActiveOptions with exact: true to require a full match:

<a routerLink="/" routerLinkActive="active"
   [routerLinkActiveOptions]="{ exact: true }">Home</a>

You can also read the active state into a template variable to drive other UI, such as aria-current for accessibility:

<a routerLink="/products"
   routerLinkActive
   #rla="routerLinkActive"
   [attr.aria-current]="rla.isActive ? 'page' : null">
  Products
</a>

Directive reference

Directive / inputPurpose
routerLinkThe target route as a string or link parameters array
queryParamsObject of query string parameters to append
fragmentURL fragment (the part after #)
queryParamsHandling'merge' or 'preserve' existing query params
stateArbitrary state passed via the History API, read later from navigation
routerLinkActiveCSS class(es) applied while the link is active
routerLinkActiveOptions{ exact: boolean } controlling match strictness

A typical rendered navigation bar produces clean, reload-free transitions:

Output:

GET /            → AppComponent + HomeComponent
click "Products" → URL changes to /products, HomeComponent destroyed,
                   ProductsComponent rendered in the outlet (no network reload)
"Products" link  → class="active strong", aria-current="page"

Best practices

  • Use routerLink (string or array) for all in-app navigation; reserve href for external URLs.
  • Always pass [routerLinkActiveOptions]="{ exact: true }" on the root / link so it isn’t perpetually active.
  • Prefer relative links inside reusable feature components so they don’t break when the route tree changes.
  • Use the array form of routerLink for any URL containing dynamic segments — it encodes each part correctly.
  • Mirror routerLinkActive with aria-current="page" for accessible navigation.
  • Import RouterOutlet, RouterLink, and RouterLinkActive explicitly in standalone components rather than relying on a shared module.
Last updated June 14, 2026
Was this helpful?