npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skitionek/route-with-dynamic-outlets

v1.1.4

Published

A template for creating npm packages using TypeScript and VSCode

Readme

route-with-dynamic-outlets

Build Status npm version Downloads Issues Code Coverage Semantic Release Commitizen Friendly Demo

Larger Angular applications often need panels or tabs interfaces where each panel is driven by the URL. Most existing implementations use a single route and manage panel state on top of it — this works for simple cases but loses all the advanced features the Angular Router offers (lazy loading, guards, nested routes, etc.).

route-with-dynamic-outlets solves this by dynamically registering named <router-outlet> children at match time, so every panel is a first-class Angular route. The library is a focused proof of concept that shows a clean, router-native direction for advanced panel/tab UIs.

👉 View the interactive demo

Getting started

Install

npm install @skitionek/route-with-dynamic-outlets

Local development and CI target Node.js 22.14 or newer.

Development container

This repository includes a dev container configuration in .devcontainer/devcontainer.json.

In VS Code, open the command palette and run:

Dev Containers: Reopen in Container

The container uses Node.js 22, installs dependencies with npm ci, and forwards port 4173 for the local docs demo.

Run demo locally

npm run demo

Then open http://localhost:4173. This serves the interactive demo from the docs/ directory.

Debug in VS Code

The workspace includes launch configurations for debugging Jest and individual TypeScript files:

Debug Current TS File
Debug Jest Tests
Debug Jest Current File

Open the Run and Debug panel in VS Code and pick the configuration that matches the file or test suite you want to inspect.

Usage

1. Create a component that renders outlets dynamically

import { Component } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { map, Observable, switchMap } from 'rxjs';
import { CommonModule } from '@angular/common';
import { OutletsMap } from '@skitionek/route-with-dynamic-outlets';

@Component({
  standalone: true,
  selector: 'app-dynamic-outlets',
  template: `
    <a [routerLink]="[{ outlets: { A: [''], B: [''] } }]">Open A and B</a>
    <router-outlet
      *ngFor="let outlet of outlets$ | async"
      [name]="outlet"
    ></router-outlet>
  `,
  imports: [RouterModule, CommonModule],
})
export class DynamicOutletsComponent {
  constructor(protected activatedRoute: ActivatedRoute) {}

  outlets$ = this.activatedRoute.data.pipe(
    switchMap(({ outlets$ }) => outlets$ as Observable<OutletsMap>),
    map(outlets => Object.keys(outlets ?? {}))
  );
}

2. Define routes with createRouteWithDynamicOutlets

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { createRouteWithDynamicOutlets } from '@skitionek/route-with-dynamic-outlets';
import { DynamicOutletsComponent } from './dynamic-outlets.component';
import { PlaceholderComponent } from './placeholder/placeholder.component';

const routes: Routes = [
  createRouteWithDynamicOutlets({
    path: 'workspace',
    component: DynamicOutletsComponent,
    dynamicOutletFactory: () => ({
      path: '',
      component: PlaceholderComponent,
    }),
  }),
];

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

Nesting

Routes with dynamic outlets can be nested arbitrarily. Pass the result of an inner createRouteWithDynamicOutlets call as the return value of dynamicOutletFactory:

createRouteWithDynamicOutlets({
  path: 'a',
  component: DynamicOutletsComponent,
  dynamicOutletFactory: () =>
    createRouteWithDynamicOutlets({
      path: 'b',
      component: DynamicOutletsComponent,
      dynamicOutletFactory: () => ({
        path: 'c',
        component: PlaceholderComponent,
      }),
    }),
})

Custom matcher

Supply a matcher function instead of (or in addition to) a path to match non-standard URL patterns:

createRouteWithDynamicOutlets({
  component: DynamicOutletsComponent,
  matcher: url => {
    if (url.length === 1 && url[0].path.match(/^@[\w]+$/)) {
      return {
        consumed: url,
        posParams: { username: new UrlSegment(url[0].path.slice(1), {}) },
      };
    }
    return null;
  },
  dynamicOutletFactory: () => ({
    path: '',
    component: PlaceholderComponent,
  }),
})

How it works

createRouteWithDynamicOutlets wraps the Angular Route object with a custom matcher. Each time the router evaluates the route, the matcher:

  1. Runs the underlying path/matcher check (using Angular's built-in defaultUrlMatcher algorithm by default).
  2. Reads the current named-outlet children from the UrlSegmentGroup.
  3. Calls dynamicOutletFactory for any new outlets and removes routes for outlets that are no longer present.
  4. Pushes the updated OutletsMap into a BehaviorSubject stored on route.data.outlets$.

Your component subscribes to activatedRoute.data.outlets$ and renders a <router-outlet> for each key, giving every outlet a full, independent router lifecycle.

API

createRouteWithDynamicOutlets(route: RouteWithDynamicOutlets): Route

Creates an Angular Route with a custom matcher that dynamically manages child outlet routes.

| Parameter | Type | Description | |-----------|------|-------------| | route | RouteWithDynamicOutlets | Angular Route extended with dynamicOutletFactory. path and matcher are both optional and behave the same as in a standard Route. |

RouteWithDynamicOutlets

Extends Angular's Route with one additional field:

| Field | Type | Description | |-------|------|-------------| | dynamicOutletFactory | DynamicOutletRouteFactory | Called once per new outlet name to build the child route definition for that outlet. |

DynamicOutletRouteFactory

type DynamicOutletRouteFactory = (
  segments: UrlSegment[],
  group: UrlSegmentGroup,
  route: Route,
  outlet: string
) => Omit<Route, 'outlet'>;

Receives the current URL segments, segment group, parent route, and the outlet name. Returns a partial Route (the outlet field is set automatically).

OutletsMap

type OutletsMap = UrlSegmentGroup['children'];

A map of outlet name → UrlSegmentGroup reflecting the currently active named outlets. Emitted by the outlets$ observable on activatedRoute.data.