๐ฆA route management library + pattern for Angular Router
A route manager and pattern for Angular
In short, this is an add-on to the @angular/router, which provides state-based routing for medium to large-size applications.
Read more about Routeshub on the docs site
To get started, you need to install the package from npm
npm install routeshub
...
export const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: AppComponent
},
{
path: 'about',
loadChildren: () => import('example-app/app/views/about/about.module').then(m => m.AboutModule)
},
{
path: '**',
redirectTo: ''
}
];
import { Note } from 'routeshub';
export interface AppNotes {
root: Note; // === ''
about: Note; // === 'about'
wildcard: Note; // === '**'
}
export const APP_NOTES_KEY = Symbol();
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { NavigationModule, connectFeatures, createRoot } from 'routeshub';
import { routes } from './app.routes';
import { APP_NOTES_KEY, AppNotes } from './app.notes';
import { aboutUnit } from '../views/about/hub/about.hub';
/**
* Creates stateful named App routes
*/
createRoot<AppNotes>(routes, { key: APP_NOTES_KEY });
/**
* connects features which have attached relating to the parent module
*
* about module has its routes which we want to connect
*/
connectFeatures<AppNotes>(APP_NOTES_KEY, {
about: aboutUnit
});
/**
* Routing module contains its configuration
* and providers (resolvers, guard, interceptors, etc.)
*
* Exports RouterModule
* and NavigationModule for navLink directives
*/
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule, NavigationModule]
})
export class AppHub {}
...
import { getUnit, Unit, Secluded } from 'routeshub';
import { AppNotes, APP_NOTES_KEY } from '../hub/app.notes';
@Component({
selector: 'app-header',
template: `
<nav>
<a navLink="{{ app.root.state }}">Home</a>
<a [navLink]="app.about.root.state">Location</a>
</nav>
`
})
export class HeaderComponent {
// getting unit by key
@Secluded(APP_NOTES_KEY)
public app: Unit<AppNotes>;
//or
// getting the unit by name
@Secluded('app')
public app: Unit<AppNotes>;
//or
// getting unit from getUnit function
public app = getUnit<AppNotes>(APP_NOTES_KEY);
}
You can find a more complex example in this repo here or on GitBook.
Routeshub offers an approach (pattern) to structure routing in the application. You can explore it in this repo.
Guideline:
Unit
is a modular entity which contains stateful module routes.
There are two ways to create the unit
:
Each creator takes the routes: Routes
and an object of options
Root creator invokes only once to initialize the hub in the application. createRoot
takes initial appNotes
.
Usage example:
createRoot<AppNotes, AppChildNotes>(routes, {
key: APP_NOTES_KEY,
routeName: { root: 'home', wildcard: 'notFound' },
nearby: { map: mapUnit }
});
In turn, the feature creator is responsible for creating lazy units (connectors), which should connect to the parent unit.
export const routes: Routes = [
{ path: '', component: AboutComponent }
];
/**
* when module has only one root ('') path
* you can declare short type
*/
export type AboutNotes = Root;
const ABOUT_NOTES_KEY = Symbol();
export const aboutConnector: Connector<AboutNotes> = createFeature<AboutNotes>(aboutNote, { key: ABOUT_NOTES_KEY });
You need to understand how routeshub transforms routes into the keys. All notes related things routeshub handles internally.
The Note
is input to reproduce the unit. Each Note
represents one route.
In short, it assigns the names to the โspotsโ
The example below shows capabilities and illustrates how this works. Unnecessary route information shortened.
export const routes: Routes = [
{
path: '', // name => 'root' by default (customizable)
children: [
{ path: '' }, // name => 'root' by default (customizable)
{ path: 'about' } // name => about
]
},
{ path: ':first_name'}, // name => firstName
{ path: 'person/:person-age' }, // name => personAge
{ path: '**'} // name => 'wildcard' by default (customizable). In example we'll rename it to 'notFound'
];
// Root interface helps to short frequently repeated actions
export interface AppChildNotes extends Root {
about: Note;
}
/**
* it is equivalent of the previous interface
*
export interface AppChildNotes {
root: Note;
about: Note;
}
*/
/**
* btw, this shorthand provides an opportunity to describe root children interface through generics
*/
export interface AppNotes extends Root<AppChildNotes> {
firstName: Note;
personAge: Note;
notFound: Note;
}
/**
* it is equivalent of the previous interface
*
export interface AppNotes {
root: Note<AppChildNotes>
firstName: Note;
personAge: Note;
wildcard: Note;
}
*/
export const appUnit = createRoot<AppNotes, AppChildNotes>(routes, { routeName: { wildcard: 'notFound' }});
Essentially, you need the unit to pass it into a directive/decorator for navigation purposes.
There are several ways to get a unit:
@Component({
...
})
export class HeaderComponent {
// getting unit by key
@Seclude(APP_NOTES_KEY)
private app: Unit<AppNotes, AppChildNotes>;
// getting unit by unit name
@Seclude('about')
private about: Unit<AboutNotes>;
}
@Component({
...
})
export class HeaderComponent {
// getting unit by key
private app = getUnit<AppNotes, AppChildNotes>(APP_NOTES_KEY);
// getting unit by name
private about = getUnit<AboutNotes>('about');
}
@Component({
...
})
export class HeaderComponent {
public hub: Units<Hub> = getRegisteredUnits<Hub>();
}
Routeshub provides directives and functions to make your experience with navigation better.
Before you start, donโt forget to import NavigationModule.
Itโs a good practice importing NavigationModule into the [module].hub.ts file.
<!--dynamic route where you deal with { path: ':id' }-->
<li [navLink]="locationUnit.profile.state"
[navParams]="{id: USER_ID}">
Profile
</li>
<!--with curly braces-->
<li navLink="{{locationUnit.map.state}}">Map</li>
<!--with square brackets-->
<li [navLink]="locationUnit.map.state">Map</li>
<!--with square brackets you can omit state props-->
<li [navLink]="locationUnit.map">Map</li>
<!--with active link-->
<li [navLink]="locationUnit.map.state"
navLinkActive="my-active-class">
Map
</li>
A function that inserts parameters into the routeโs state and outputs a ready-made dynamic state.
...
@Component({
...
})
export class ExampleComponent {
...
constructor(private router: Router) {}
public navigateToProfile(): void {
const toProfile = forwardParams(this.userUnit.profileId.state, { profileId: 77 })
this.router.navigate(toProfile);
}
}
Stay tuned with changelog.
This project is MIT licensed.
Contributions are welcome. For major changes, please open an issue first to discuss what you would like to change.
If you made a PR, make sure to update tests as appropriate and keep the examples consistent.
Thanks go to these wonderful people (emoji key):
Max Tarsis ๐ค ๐ป ๐ ๐ ๐ |
Lars Gyrup Brink Nielsen ๐ ๐ค |
Denis Makarov ๐ป ๐ค |
This project follows the all-contributors specification. Contributions of any kind welcome!