In Angular, how do you open a compose panel in a named router-outlet beside the inbox, and what does the resulting URL look like?
answer
- name attribute on the outlet
- outlet property on the route
- outlets command object
- parentheses in the URL
- null closes it
basics
~10 sAdd <router-outlet name="side" />, give a route outlet: 'side', and navigate with an outlets command such as [{outlets: {side: ['compose']}}]. The URL becomes /inbox(side:compose); setting side to null closes the panel.
solid answer
~40 sA named outlet is `<router-outlet name="side" />`, next to the unnamed primary outlet. Routes target it with the `outlet` property: `{path: 'compose', component: Compose, outlet: 'side'}`. To open it, navigate with an outlets command: `[routerLink]="[{outlets: {side: ['compose']}}]"` or `router.navigate([{outlets: {side: ['compose']}}])`. The serializer writes secondary outlets in parentheses after the primary path: `/inbox(side:compose)`; several are joined with `//`. Because the side panel is in the URL, it survives reload and deep links. Primary navigations built with `routerLink` or `navigate()` are deltas, so `/sent` from there keeps `(side:compose)`, while `navigateByUrl('/sent')` replaces the whole URL and closes it. `{outlets: {side: null}}` removes that outlet.
code
ts · 15 linesimport {Component} from '@angular/core';
import {RouterLink, RouterOutlet} from '@angular/router';
// routes: [{path: 'inbox', component: Inbox}, {path: 'compose', component: Compose, outlet: 'side'}]
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink],
template: `
<a [routerLink]="[{outlets: {side: ['compose']}}]">Compose</a>
<a [routerLink]="[{outlets: {side: null}}]">Close panel</a>
<main><router-outlet /></main>
<aside><router-outlet name="side" /></aside>
`,
})
export class App {}go deeper
Know that an outlet can have a name, that routes target it with the outlet property, and that it shows up in the URL.
Write the outlets command to open and close a panel, read the parenthesised URL syntax, and place the outlet route at the right level.
Explain why delta navigation keeps secondary outlets while navigateByUrl drops them, and fix panels that close unexpectedly.
Judge whether a panel deserves URL state at all, weighing deep links and Back behaviour against unusual URLs and extra routing complexity.
## Why named outlets exist Most screens have one routed area, the **primary** outlet. Some need a second, independently routed area: a compose panel beside the inbox, a chat drawer, a help pane. Angular's router supports this with **named (secondary) outlets**: each one has its own route branch and its own part of the URL, so the panel can be opened, closed, reloaded and deep-linked without touching the main view. ## The three pieces 1. **The outlet** - in the template, `<router-outlet />` (primary, named `'primary'`) and `<router-outlet name="side" />`. 2. **The route** - `{path: 'compose', component: Compose, outlet: 'side'}` in the same `Routes` array level as the routes of the component that holds that outlet. 3. **The navigation** - an `outlets` command naming the outlet. ```ts // open this.router.navigate([{outlets: {side: ['compose']}}]); // close this.router.navigate([{outlets: {side: null}}]); ``` ## The URL syntax The default `UrlSerializer` writes secondary outlets in parentheses, as `name:path`, joined by `//`: | State | URL | |---|---| | inbox only | `/inbox` | | inbox plus compose panel | `/inbox(side:compose)` | | plus a second outlet `help` | e.g. `/inbox(side:compose//help:shortcuts)` | | outlets under a `mail` parent | `/mail/(inbox//side:compose)` | The last row appears when the named outlet lives inside a child component, `MailShell`, rather than the root: the primary child and the named child are grouped in parentheses under `mail/`. The route config mirrors it: the `outlet: 'side'` route goes into `mail`'s `children`. ## What keeps and what drops the panel `routerLink` and `Router.navigate()` build the new URL as a **delta** of the current URL tree. A command that changes only the primary outlet leaves other outlets alone: - `<a routerLink="/sent">` from `/inbox(side:compose)` goes to `/sent(side:compose)`: the panel stays open; - `router.navigateByUrl('/sent')` parses a complete URL with no secondary part: the panel closes; - `{outlets: {primary: ['sent'], side: null}}` changes both in one navigation. This is a common source of "the side panel randomly closes" bugs: some code paths navigate by URL string and others by commands. ## Why the URL encoding matters - **Reload and share.** The panel state is in the address, so a reload of `/inbox(side:compose)` restores both views. - **Back button.** Opening and closing the panel are navigations, so Back undoes them; use `replaceUrl: true` if a panel toggle should not add history entries. - **Guards and resolvers** apply to the outlet's route like any other, so a `canDeactivate` guard on `Compose` can protect an unsaved draft when the panel closes. ## Reading an outlet's state Inside the panel component, `inject(ActivatedRoute)` returns the route of the `side` branch, with its own params and data; the primary view's route is a sibling in the router state tree, reachable through `router.routerState.root.children`. Each outlet also has its own lifecycle: opening the panel creates `Compose` and fires the side outlet's `activate`, and closing it destroys `Compose` without touching the inbox. ## Limits and alternatives - Every outlet name at one level must be unique. - The parenthesised URLs look unusual to users and analytics; if the panel does not need to be linkable, a query param or plain component state may be simpler. - An outlet's route must be defined at the level of the component that renders that outlet; a route at the wrong level simply never matches, which surfaces as a navigation error whose message starts "Cannot match any routes. URL Segment:" and names the outlet's segment.
- Why does the side panel stay open after clicking a routerLink to /sent but close after navigateByUrl('/sent')?routerLink and navigate() build a delta of the current URL tree, changing only the primary outlet and keeping others. navigateByUrl() takes a complete URL; '/sent' has no secondary part, so the side outlet is dropped.
- Where must the route for a named outlet be declared when the outlet sits inside MailShell?In MailShell's route children, next to the primary child routes, with outlet: 'side'. The URL then groups them under the parent, as /mail/(inbox//side:compose).
saying these in an interview costs you the question
- Opening a named outlet with routerLink="side:compose" as a plain string
- Expecting named outlet state to be lost on page reload
- Believing every primary navigation closes secondary outlets
- Declaring the outlet route at the root when the outlet lives in a child component
- Closing a panel by navigating to an empty path instead of null