In an Angular mail client, why does routerLink="../sent" inside the side outlet's Compose component change the side panel instead of the main view, and how do you target the primary outlet?
answer
- relative to whose route
- the side outlet's segment group
- absolute keeps other outlets
- name the outlet explicitly
basics
~20 sA relative link resolves against the ActivatedRoute of the component it sits in, and Compose's route is the side outlet's branch, so '../sent' rewrites that branch to (side:sent). Use '/sent' or an outlets command naming primary.
solid answer
~40 s`RouterLink` resolves relative commands against the `ActivatedRoute` injected where it sits. `Compose` is rendered by the `side` outlet, so its route is the `compose` node of the **side** branch, and the relative command edits that branch: `../sent` turns `/inbox(side:compose)` into `/inbox(side:sent)`, and `sent` without `../` gives `(side:compose/sent)`. Neither matches a `side` route, so the navigation fails or opens the wrong panel. To change the main view from inside the panel, use an absolute command: `routerLink="/sent"` goes to `/sent(side:compose)` and keeps the panel. To also close it, name both outlets: `[routerLink]="['/', {outlets: {primary: ['sent'], side: null}}]"`, or `router.navigate([{outlets: {primary: ['sent'], side: null}}])` from code, which is root-relative by default.
code
ts · 17 linesimport {Component} from '@angular/core';
import {RouterLink} from '@angular/router';
// Rendered in <router-outlet name="side" /> on /inbox(side:compose)
@Component({
selector: 'app-compose',
imports: [RouterLink],
template: `
<!-- Wrong: resolves in the side branch -> /inbox(side:sent) -->
<a routerLink="../sent">Sent (broken)</a>
<!-- Main view to Sent, panel stays: /sent(side:compose) -->
<a routerLink="/sent">Sent</a>
<!-- Main view to Sent, panel closes: /sent -->
<a [routerLink]="['/', {outlets: {primary: ['sent'], side: null}}]">Sent and close</a>
`,
})
export class Compose {}go deeper
Recall that links without a leading slash are relative to the component's own route, and that a named outlet has its own route.
Explain how the side outlet provides Compose's ActivatedRoute and why the relative command edits the side segment group.
Fix it with absolute or outlets commands, keep relative links for panel-internal moves, and catch it early by reading rendered hrefs.
Define how panels and hosts exchange destinations, UrlTrees or callbacks, so secondary-outlet components stay reusable across layouts.
## The setup The app root has a primary outlet and an outlet named `side`. Routes: ```ts export const routes: Routes = [ {path: 'inbox', component: Inbox}, {path: 'sent', component: Sent}, {path: 'compose', component: Compose, outlet: 'side'}, ]; ``` The user is on `/inbox(side:compose)`. `Compose` has a "Show sent mail" link written as `routerLink="../sent"`, which works in every primary-outlet component, and here it does something else. ## Why the link targets the panel `RouterLink` builds its URL with `Router.createUrlTree()`, passing its own `ActivatedRoute` as `relativeTo`. That route is the one the **outlet** gave the component: 1. The `side` outlet created `Compose` with an injector that provides the `compose` route's `ActivatedRoute`. 2. The router turns that route into its URL segment group, the `side` group holding `compose`. 3. Relative commands are applied **inside that group**: `../` removes `compose`, `sent` is appended there. Result: `/inbox(side:sent)`. Unless some `side` route matches `sent`, such as a wildcard, navigation fails with a no-match error; if one does match, the wrong panel opens. Writing `sent` without `../` is worse: `(side:compose/sent)`. This is the same rule as everywhere else, relative means relative to the component's own route, but in a named outlet "own route" is a different branch of the URL tree from the one users think of as the page. ## Targeting the primary outlet | Goal | Command | |---|---| | show Sent, keep panel | `routerLink="/sent"` | | show Sent, close panel | `[routerLink]="['/', {outlets: {primary: ['sent'], side: null}}]"` | | same, from code | `router.navigate([{outlets: {primary: ['sent'], side: null}}])` | | change only the panel | `[routerLink]="[{outlets: {side: ['help']}}]"` or a relative link | Why these work: - An absolute command starts at the URL root and, as a delta, replaces only the primary branch, so `/sent` keeps `(side:compose)`. - An `outlets` object names each outlet explicitly; `null` removes one. - `Router.navigate()` without `relativeTo` is root-relative, so an outlets command there addresses the top-level outlets directly. ## Making it robust - **Treat panel components as guests.** Links that change the main view should be absolute or built by the host, for example as `UrlTree` inputs passed into the panel. - **Keep relative links for panel-internal navigation**, such as moving from `compose` to `compose/preview` inside the same outlet, where resolving against the panel's branch is exactly right. - **Check the rendered `href`**. On an anchor, `routerLink` writes the resolved URL; `/inbox(side:sent)` in the element inspector explains the bug before anyone clicks. - **Test with the panel open.** A test that renders `Compose` only in the primary outlet will pass, because the relative link then resolves against the primary branch. ## Why the router works this way The URL tree is a tree of segment groups, one branch per outlet, and every `ActivatedRoute` knows which group it came from. Resolving relative commands against the component's own group is what makes panels self-contained: a wizard in the side outlet can step through `step1`, `step2` and `step3` with plain relative links and never disturb the main view. The price is that crossing into another outlet must always be explicit. ## Nested variant If the outlets live inside a `mail` shell, the URL is `/mail/(inbox//side:compose)` and the same logic applies one level down: relative links in `Compose` edit the `side` group under `mail`, and absolute ones must include `mail`, as in `/mail/sent`.
- When is a relative link inside a named-outlet component the right choice?When the navigation should stay inside that panel, such as moving from compose to compose/preview. The link then edits only the side branch and leaves the primary view and its state alone.
- How would you let a panel component trigger a primary navigation without knowing the app's URLs?Have the host pass a UrlTree or an output callback into the panel. The host builds the destination with createUrlTree(), so the panel stays reusable and never guesses which branch it lives in.
A note left in the side office saying 'next door' means next door to the side office, not to the reception desk. Directions are read from where the note is pinned.
saying these in an interview costs you the question
- Assuming relative links always resolve against the main page's route
- Believing an absolute link from the panel always closes the panel
- Closing a panel by navigating the side outlet to an empty path
- Testing the panel component only in the primary outlet
- Blaming the route table when the rendered href shows the side branch