{"id":15103498,"url":"https://github.com/kioviensis/routeshub","last_synced_at":"2025-09-27T01:30:58.812Z","repository":{"id":37025509,"uuid":"156601640","full_name":"tarsisexistence/routeshub","owner":"tarsisexistence","description":"🚦A route management library + pattern for Angular Router","archived":true,"fork":false,"pushed_at":"2023-12-21T21:22:00.000Z","size":5594,"stargazers_count":47,"open_issues_count":12,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-02T18:09:31.441Z","etag":null,"topics":["angular","hub","javascript","management","routes","routing","rxjs","state","store","typescript"],"latest_commit_sha":null,"homepage":"https://routeshub.gitbook.io","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tarsisexistence.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-11-07T20:04:59.000Z","updated_at":"2024-08-27T16:07:58.000Z","dependencies_parsed_at":"2023-10-15T14:01:34.599Z","dependency_job_id":"0dcf38b5-b39e-48ca-9244-7fa3c1d7c3a0","html_url":"https://github.com/tarsisexistence/routeshub","commit_stats":null,"previous_names":["retarsis/routeshub","tarsisexistence/routeshub"],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarsisexistence%2Frouteshub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarsisexistence%2Frouteshub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarsisexistence%2Frouteshub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarsisexistence%2Frouteshub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tarsisexistence","download_url":"https://codeload.github.com/tarsisexistence/routeshub/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234369997,"owners_count":18821367,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","hub","javascript","management","routes","routing","rxjs","state","store","typescript"],"created_at":"2024-09-25T19:24:23.558Z","updated_at":"2025-09-27T01:30:58.440Z","avatar_url":"https://github.com/tarsisexistence.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Routeshub\n\n\u003ca href=\"https://routeshub.gitbook.io/docs\"\u003e\u003cimg src=\"https://github.com/retarsis/routeshub/raw/master/docs/assets/logo.png\" align=\"right\" alt=\"\"\u003e\u003c/a\u003e\n\nA **route manager** and pattern for **[Angular]**\n\n* **Manager.** Introduces a new route management experience that leads to better application control.\n* **Navigation.** It provides declarative navigation experience.\n* **Fast.** In addition to speeding up development, it works as fast as it does without it.\n* **Pluggable.** Engineered as a plug-in and designed to be added at any time during the development process.\n* **Pattern.** It provides a unified approach managing the routing of the entire application.\n* **Small.** ~3.5 kB (minified + gzipped). It uses [Angular] and [rxjs] as peerDependencies.\n\nIn short, this is an add-on to the **@angular/router**, which provides state-based routing for medium to large-size applications.\n\nRead more about Routeshub on the [docs site](https://routeshub.gitbook.io)\n\n\u003cbr/\u003e\n\n## Install\nTo get started, you need to install the package from npm\n```sh\nnpm install routeshub\n```\n\n## Usage\n\n#### Declare a simple **[module]**.routes.ts file\n```typescript\n...\n\nexport const routes: Routes = [\n  {\n    path: '',\n    pathMatch: 'full',\n    component: AppComponent\n  },\n  {\n    path: 'about',\n    loadChildren: () =\u003e import('example-app/app/views/about/about.module').then(m =\u003e m.AboutModule)\n  },\n  {\n    path: '**',\n    redirectTo: ''\n  }\n];\n```\n\n#### Getting interface based on our routes and unique unit key (as symbol) in **[module]**.notes.ts\n```typescript\nimport { Note } from 'routeshub';\n\nexport interface AppNotes {\n  root: Note; // === ''\n  about: Note; // === 'about'\n  wildcard: Note; // === '**'\n}\n\nexport const APP_NOTES_KEY = Symbol();\n```\n\n#### Creating a hub **[module]**.hub.ts file\n```typescript\nimport { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { NavigationModule, connectFeatures, createRoot } from 'routeshub';\nimport { routes } from './app.routes';\nimport { APP_NOTES_KEY, AppNotes } from './app.notes';\nimport { aboutUnit } from '../views/about/hub/about.hub';\n\n/**\n * Creates stateful named App routes\n */\ncreateRoot\u003cAppNotes\u003e(routes, { key: APP_NOTES_KEY });\n\n/**\n * connects features which have attached relating to the parent module\n *\n * about module has its routes which we want to connect\n */\nconnectFeatures\u003cAppNotes\u003e(APP_NOTES_KEY, {\n  about: aboutUnit\n});\n\n/**\n * Routing module contains its configuration\n * and providers (resolvers, guard, interceptors, etc.)\n * \n * Exports RouterModule\n * and NavigationModule for navLink directives\n */\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule, NavigationModule]\n})\nexport class AppHub {}\n```\n\n\n#### Component\n```typescript\n...\n\nimport { getUnit, Unit, Secluded } from 'routeshub';\nimport { AppNotes, APP_NOTES_KEY } from '../hub/app.notes';\n\n@Component({\n  selector: 'app-header',\n  template: `\n  \u003cnav\u003e\n    \u003ca navLink=\"{{ app.root.state }}\"\u003eHome\u003c/a\u003e\n    \u003ca [navLink]=\"app.about.root.state\"\u003eLocation\u003c/a\u003e\n  \u003c/nav\u003e\n`\n})\nexport class HeaderComponent {\n  // getting unit by key\n  @Secluded(APP_NOTES_KEY)\n  public app: Unit\u003cAppNotes\u003e;\n  \n  //or\n  \n   // getting the unit by name\n   @Secluded('app')\n   public app: Unit\u003cAppNotes\u003e;\n   \n   //or\n   \n   // getting unit from getUnit function\n   public app = getUnit\u003cAppNotes\u003e(APP_NOTES_KEY);\n}\n```\n\n[Angular]: https://github.com/storeon/angular\n[rxjs]: https://github.com/ReactiveX/rxjs\n\nYou can find a more complex example in this [repo](https://github.com/retarsis/routeshub/tree/master/example-app)  here or on [GitBook](https://routeshub.gitbook.io/docs/example).\n\n\u003cbr/\u003e\n\n# Hub\nRouteshub offers an approach (pattern) to structure routing in the application. You can explore it in this [repo](https://github.com/retarsis/routeshub/tree/master/example-app).\n\n**Guideline**:\n- Each module in the application has a hub directory\n- hub directory contains three files that configure the routing of the current module.\n1. hub - sets routing configuration; exports RoutingModule and NavigationModule; connects feature units.\n2. notes - it is a place for interfaces and the unique key of the unit.\n3. routes - simple routes file as is without any changes.\n\n![diagram](http://i.piccy.info/i9/baf248d94b1260e13d5bff6acb6e6727/1569138770/128577/1338898/2019_09_22_10_51_00.jpg)\n\n\u003cbr/\u003e\n\n# Concepts\n\n## Unit\n`Unit` is a modular entity which contains stateful module routes.\n\n![unit diagram](http://i.piccy.info/i9/395309194dfc663ad7c07dad3c482d15/1569140574/118208/1338898/unit.jpg)\n\nThere are two ways to create the `unit`:\n- **createRoot**\n- **createFeature**\n\nEach creator takes the `routes: Routes` and an object of options \n- key - unit identifier and accepts string or symbol\n- routeName - accepts an object with optional custom names for a wildcard ('**') and root ('') paths\n- nearby - accepts lazy units (**connectors**), which are outputs of  **feature creator**. A nearby option should use when one or more connected features are eager modules with their own routes files.\n\n**Root** creator invokes only once to initialize the hub in the application. `createRoot` takes initial `appNotes`. \n\n**Usage example**:\n```typescript\n createRoot\u003cAppNotes, AppChildNotes\u003e(routes, {\n  key: APP_NOTES_KEY,\n  routeName: { root: 'home', wildcard: 'notFound' },\n  nearby: { map: mapUnit  }\n });\n```\n\nIn turn, the **feature** creator is responsible for creating lazy units (**connectors**), which should connect to the parent unit.\n\n```typescript\nexport const routes: Routes = [\n  { path: '', component: AboutComponent }\n];\n\n/** \n  * when module has only one root ('') path\n  * you can declare short type\n*/\nexport type AboutNotes = Root;\n\nconst ABOUT_NOTES_KEY = Symbol();\n\nexport const aboutConnector: Connector\u003cAboutNotes\u003e = createFeature\u003cAboutNotes\u003e(aboutNote, { key: ABOUT_NOTES_KEY });\n```\n\n\u003cbr/\u003e\n\n## Note\nYou need to understand how routeshub transforms routes into the keys. All notes related things routeshub handles internally.\n\nThe `Note` is input to reproduce the unit. Each `Note` represents one route.\n\n**In short, it assigns the names to the 'spots'**\n\nThe example below shows capabilities and illustrates how this works. Unnecessary route information shortened.\n\n```typescript\nexport const routes: Routes = [\n  {\n    path: '', // name =\u003e 'root' by default (customizable)\n    children: [\n      { path: '' }, // name =\u003e 'root' by default (customizable)\n      { path: 'about' } // name =\u003e about \n    ]\n  },\n  { path: ':first_name'}, // name =\u003e firstName\n  { path: 'person/:person-age' }, // name =\u003e personAge\n  { path: '**'} // name =\u003e 'wildcard' by default (customizable). In example we'll rename it to 'notFound'\n];\n\n// Root interface helps to short frequently repeated actions\nexport interface AppChildNotes extends Root {\n  about: Note;\n}\n\n/**\n  * it is equivalent of the previous interface\n  * \n  export interface AppChildNotes {\n    root: Note;\n    about: Note;\n  }\n*/\n\n/**\n  * btw, this shorthand provides an opportunity to describe root children interface through generics\n*/\nexport interface AppNotes extends Root\u003cAppChildNotes\u003e {\n  firstName: Note;\n  personAge: Note;\n  notFound: Note;\n}\n\n/**\n  * it is equivalent of the previous interface\n  * \n  export interface AppNotes {\n    root: Note\u003cAppChildNotes\u003e\n    firstName: Note;\n    personAge: Note;\n    wildcard: Note;\n  }\n*/\n\n\nexport const appUnit = createRoot\u003cAppNotes, AppChildNotes\u003e(routes, { routeName: { wildcard: 'notFound' }});\n```\n\n\u003cbr/\u003e\n\n## Get Unit\nEssentially, you need the unit to pass it into a directive/decorator for navigation purposes.\nThere are several ways to get a unit:\n\n-  **@United decorator**. Apply this decorator on the component's prop. You should pass the key or unit name.\n```typescript\n@Component({\n  ...\n})\nexport class HeaderComponent {\n  // getting unit by key\n  @Seclude(APP_NOTES_KEY)\n  private app: Unit\u003cAppNotes, AppChildNotes\u003e;\n\n  // getting unit by unit name\n  @Seclude('about')\n  private about: Unit\u003cAboutNotes\u003e;\n}\n```\n\n-  **get unit** - This is a function that works as a decorator. Primarily, it created as an alternative to @Secluded decorator.\n```typescript\n@Component({\n  ...\n})\nexport class HeaderComponent {\n  // getting unit by key\n  private app = getUnit\u003cAppNotes, AppChildNotes\u003e(APP_NOTES_KEY);\n\n  // getting unit by name\n  private about = getUnit\u003cAboutNotes\u003e('about');\n}\n```\n\n-  **getRegisteredUnits** - This is a function that returns all declared units in the project.\n```typescript\n@Component({\n  ...\n})\nexport class HeaderComponent {\n  public hub: Units\u003cHub\u003e = getRegisteredUnits\u003cHub\u003e();\n}\n```\n\n\u003cbr/\u003e\n\n## Navigation\n**Routeshub** provides directives and functions to make your experience with navigation better.\n\nBefore you start, don't forget to import **NavigationModule**.\nIt's a good practice importing NavigationModule into the **[module]**.hub.ts file.\n\n\n### navLink\n```html\n\u003c!--dynamic route where you deal with { path: ':id' }--\u003e\n\u003cli [navLink]=\"locationUnit.profile.state\" \n    [navParams]=\"{id: USER_ID}\"\u003e\n    Profile\n\u003c/li\u003e\n\n\n\u003c!--with curly braces--\u003e\n\u003cli navLink=\"{{locationUnit.map.state}}\"\u003eMap\u003c/li\u003e\n\u003c!--with square brackets--\u003e\n\u003cli [navLink]=\"locationUnit.map.state\"\u003eMap\u003c/li\u003e\n\u003c!--with square brackets you can omit state props--\u003e\n\u003cli [navLink]=\"locationUnit.map\"\u003eMap\u003c/li\u003e\n\n\u003c!--with active link--\u003e\n\u003cli [navLink]=\"locationUnit.map.state\" \n    navLinkActive=\"my-active-class\"\u003e\n    Map\n\u003c/li\u003e\n```\n\n### forwardParams\nA function that inserts parameters into the route's state and outputs a ready-made dynamic state. \n\n```typescript\n...\n\n@Component({\n  ...\n})\nexport class ExampleComponent {\n  ...\n\n  constructor(private router: Router) {}\n\n  public navigateToProfile(): void {\n    const toProfile = forwardParams(this.userUnit.profileId.state, { profileId: 77 })\n    this.router.navigate(toProfile);\n  }\n}\n\n```\n\n\u003cbr/\u003e\n\n## Changelog\n\nStay tuned with [changelog](https://github.com/retarsis/routeshub/blob/master/CHANGELOG.md).\n\n\u003cbr/\u003e\n\n## License\nThis project is [MIT](https://choosealicense.com/licenses/mit/) licensed.\n\n\u003cbr/\u003e\n\n## Contributing\nContributions are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\nIf you made a PR, make sure to update tests as appropriate and keep the examples consistent.\n\n\u003cbr/\u003e\n\n## Contributors ✨\n\nThanks go to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/retarsis\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/21989873?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMax Tarsis\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#ideas-retarsis\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e \u003ca href=\"https://github.com/retarsis/routeshub/commits?author=retarsis\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"#infra-retarsis\" title=\"Infrastructure (Hosting, Build-Tools, etc)\"\u003e🚇\u003c/a\u003e \u003ca href=\"https://github.com/retarsis/routeshub/commits?author=retarsis\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"#projectManagement-retarsis\" title=\"Project Management\"\u003e📆\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://medium.com/@LayZeeDK\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/6364586?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eLars Gyrup Brink Nielsen\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#infra-LayZeeDK\" title=\"Infrastructure (Hosting, Build-Tools, etc)\"\u003e🚇\u003c/a\u003e \u003ca href=\"#ideas-LayZeeDK\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/limitofzero\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/16196664?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eDenis Makarov\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/retarsis/routeshub/commits?author=limitofzero\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"#ideas-limitofzero\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n\n\u003cbr/\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkioviensis%2Frouteshub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkioviensis%2Frouteshub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkioviensis%2Frouteshub/lists"}