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

@cocoar/ui-routing

v0.1.0-beta.155

Published

Fragment-based routing utilities for Angular applications

Readme

@cocoar/ui-routing

Fragment-based routing utilities for Angular applications. Enable deep-linkable components (modals, drawers, panels) and custom actions through URL fragments.

Features

  • Fragment-as-Route Pattern — Treat URL fragments (#details/123) like declarative routes
  • Path Parameter Extraction — Use path patterns (:id, :customer) in fragments
  • Query Parameter Support — Parse and type query parameters (?edit=true&mode=advanced)
  • Component Deep-linking — Open any component via URL (modals, drawers, panels, etc.)
  • Action Handlers — Execute side effects through fragment changes
  • Framework-Agnostic — No assumptions about UI implementation (bring your own overlay system)
  • Type-Safe — Full TypeScript support with inference

Installation

npm install @cocoar/ui-routing

Quick Start

1. Define Fragment Routes

import { ComponentRoutedFragment, ActionRoutedFragment } from '@cocoar/ui-routing';

// For modals, drawers, or any component-based UI:
const fragments: ComponentRoutedFragment[] = [
  {
    type: 'component',
    path: 'details/:id',
    loadComponent: () => import('./details.component').then(m => m.DetailsComponent),
    options: { width: '800px', closeOnBackdrop: true } // Your custom config
  }
];

// For actions without UI:
const actionFragments: ActionRoutedFragment[] = [
  {
    type: 'action',
    path: 'logout',
    handler: () => authService.logout()
  }
];

2. Add to Route Configuration

import { createRouteData, IRoutedFragmentConfig } from '@cocoar/ui-routing';

const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    data: createRouteData<IRoutedFragmentConfig>({
      routedFragments: fragments
    })
  }
];

3. React to Fragment Changes

import { RoutedFragmentService } from '@cocoar/ui-routing';

@Component({ /* ... */ })
export class DashboardComponent {
  private fragmentService = inject(RoutedFragmentService);
  private modalService = inject(MyModalService); // Your modal implementation

  ngOnInit() {
    // Handle component fragments (modals, drawers, etc.)
    this.fragmentService.getParsedFragments('component').subscribe(components => {
      components.forEach(async item => {
        const component = await item.route.loadComponent();
        this.modalService.open(component, {
          data: item.params,
          ...item.route.options // Your custom options
        });
      });
    });

    // Handle action fragments
    this.fragmentService.getParsedFragments('action').subscribe(actions => {
      actions.forEach(item => item.route.handler(item.params));
    });
  }
}
```component via fragment
this.router.navigate([], { fragment: 'details/123?edit=true' });

// Multiple fragments (component + confirmation)
this.router.navigate([], { fragment: 'details/123#confirm' });

// Remove fragment (close component)
this.fragmentService.removeFragmentPart('details/123');

API Reference

Types

ComponentRoutedFragment<TOptions>

Configuration for component fragments (modals, drawers, panels, etc.) with generic options.

interface ComponentRoutedFragment<TOptions = unknown> {
  type: 'component';
  path: string; // Path pattern (e.g., 'details/:id/:tab')
  loadComponent: () => Type<unknown> | Promise<Type<unknown>>;
  options?: TOptions; // Your custom
interface ModalRoutedFragment<TModalOptions = unknown> {
  type: 'modal';
  path: string; // Path pattern (e.g., 'details/:id/:tab')
  loadComponent: () => Type<unknown> | Promise<Type<unknown>>;
  modalOptions?: TModalOptions; // Your overlay config type
}

ActionRoutedFragment

Configuration for action fragments (no UI).

interface ActionRoutedFragment extends RoutedFragmentBase<never> {
  type: 'action';
  handler: (params: any) => void;
  options?: never; // Actions don't have options
}

IRoutedFragmentConfig<TFragment>

Route data configuration. Generic parameter allows type-safe fragment arrays.

interface IRoutedFragmentConfig<TFragment extends RoutedFragmentBase = RoutedFragmentBase> {
  routedFragments: TFragment[];
}

ParsedRoute<T>

Parsed fragment with extracted parameters.

interface ParsedRoute<T extends RoutedFragment = RoutedFragment> {
  params: { [key: string]: any }; // Extracted path & query params
  route: T; // Matched route configuration
  fragment: string; // Original fragment string
}

Services

RoutedFragmentService

component fragments (modals, drawers, etc.) fragmentService.getParsedFragments('component').subscribe(components => { // Handle components });

// Get action fragments fragmentService.getParsedFragments('action').subscribe(actions => { // Handle actionmits parsed fragments filtered bycomponents).

fragmentService.removeFragmentPart('details/123');

Integration Examples

The library is completely generic and works with any UI system. Here are integration patterns:

With Modals (Angular Material Dialog)

import { MatDialogConfig } from '@angular/material/dialog';
import { ComponentRoutedFragment } from '@cocoar/ui-routing';

type ModalFragment = ComponentRoutedFragment<MatDialogConfig>;

const fragments: ModalFragment[] = [{
  type: 'component',
  path: 'details/:id',
  loadComponent: () => DetailsComponent,
  options: { width: '600px', hasBackdrop: true }
}];

// In component:
this.fragmentService.getParsedFragments('component').subscribe(items => {
  items.forEach(async item => {
    const component = await item.route.loadComponent();
    this.dialog.open(component, {
      data: item.params,
      ...item.route.options
    });
  });
});

With Drawers/Side Panels

interface DrawerConfig {
  position: 'left' | 'right';
  width: string;
}

type DrawerFragment = ComponentRoutedFragment<DrawerConfig>;

const fragments: DrawerFragment[] = [{
  type: 'component',
  path: 'settings',
  loadComponent: () => SettingsComponent,
  options: { position: 'right', width: '400px' }
}];

With Custom Overlay System

interface MyOverlayConfig {
  width?: string;
  closeOnEscape?: boolean;
  backdrop?: boolean;
}

type CustomFragment = ComponentRoutedFragment<MyOverlayConfig>;

// Create a service that bridges fragments → your overlay system
@Injectable({ providedIn: 'root' })
export class RoutedOverlayService {
  private fragmentService = inject(RoutedFragmentService);
  private overlayService = inject(MyOverlayService);

  constructor() {
    this.fragmentService.getParsedFragments('component').subscribe(items => {
      items.forEach(async item => {
        const component = await item.route.loadComponent();
        this.overlayService.open(component, {
          data: item.params,
          config: item.route.options
        });
      });
    });
  }
}loseOnEscape?: boolean;
}

type CustomModalFragment = ModalRoutedFragment<MyOverlayConfig>;

const fragments: CustomModalFragment[] = [{
  type: 'modal',
  path: 'settings',
  loadComponent: () => SettingsComponent,
  modalOptions: { width: '400px', closeOnEscape: true }
}];

Advanced Usage

Extending with Custom Fragment Types

The library uses a base interface pattern that allows you to create your own fragment types:

import { RoutedFragmentBase } from '@cocoar/ui-routing';
import { Type } from '@angular/core';

// 1. Define your custom fragment type
export interface DrawerRoutedFragment extends RoutedFragmentBase<DrawerConfig> {
  type: 'drawer';
  side: 'left' | 'right';
  loadComponent: () => Type<unknown> | Promise<Type<unknown>>;
}

export interface DrawerConfig {
  width: string;
  backdrop?: boolean;
}

// 2. Create fragments
const drawerFragments: DrawerRoutedFragment[] = [{
  type: 'drawer',
  path: 'filters',
  side: 'left',
  loadComponent: () => import('./filters-drawer.component'),
  options: { width: '300px', backdrop: true }
}];

// 3. React to your custom type
this.fragmentService.getParsedFragments('drawer').subscribe(drawers => {
  drawers.forEach(async item => {
    const component = await item.route.loadComponent();
    // item.route is typed as DrawerRoutedFragment
    this.drawerService.open(component, item.route.side, item.route.options);
  });
});

Multiple Fragments

// URL: /page#details/123#confirm
// Opens both "details" component AND "confirm" component

Query Parameters

// Fragment: details/123?edit=true&tab=settings
// Parsed params: { id: '123', edit: true, tab: 'settings' }

Dynamic Parameters

{
  type: 'component',
  path: 'user/:userId/order/:orderId',
  loadComponent: () => OrderDetailsComponent
}

// Fragment: user/42/order/1337
// Params: { userId: '42', orderId: '1337' }

Accessibility Considerations

⚠️ Important: Fragment changes do not automatically announce to screen readers. When opening components via fragments:

  1. Ensure proper ARIA attributes (role, aria-labelledby, aria-describedby)
  2. Trap focus within overlays
  3. Return focus to trigger element on close
  4. Consider announcing state changes with aria-live regions

Browser Support

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Requires Angular 21+ and Angular Router

License

Apache-2.0

Contributing

See CONTRIBUTING.md for guidelines.