@momsfriendlydevco/router

1.1.10 • Public • Published

@MomsFriendlyDevCo/Router / Angular-MFDC-Router

A non-annoying, exceedingly simple but extremely powerful router using a chainable syntax.

This module is primarily designed to work with Angular but it should be modular enough to also work with Node - see testkit.

angular
	.module('app')
	.run($router => $router.when('/widgets/:id?').component('widgetsListCtrl'))
	.component('widgetsListCtrl', {
		templateUrl: '/units/widgets/list.tmpl.html',
		controller: function($scope, $router) {
			var $ctrl = this;

			// Do something with the widget ID specified in $router.params.id
		},
	});

Features:

  • Chainable syntax - Use a simple BDD / chainable layout to define routes e.g. $router.when('/this/path').requires($session.checkLogin).component('something')
  • Regular Angular event system - none of that weird $transition system Angular-UI-Router uses. The router fires $rootScope.$broadcast('$routerSuccess') events like everything else
  • Sensible route prioritization - tokens are prioritized LAST so /foo/:id gets matched after /foo/create. Rather than creating weird hierarchical rules, you can just dump rules into the router and let it figure it out
  • Component support - Components are the future
  • Routes can be updated in real time - the router will automatically resort rules as things are added and removed
  • Promise based architecture - fits in better with the way Angular does things and allows for async handling gracefully
  • Central definition of tokens - define what commonly used capture tokens should need to validate (e.g. that :id should be a 24 bit hex string, globally)
  • "Provide" pattern proof - Angular is hard enough to understand as it is without having to reference two different classes that do seemingly different things. $router exposes exactly ONE well defined and easy to use service
  • Exceptionally small - Seriously look at the source - its one file of about 80 lines of actual code
  • Exceptionally fast - Because there is no weird cruft to handle there are no excess parts of the router that slows everything down
  • Undefined queries - No need to define all acceptable query strings. Anything contained after the ? character gets decoded and placed in $router.query which can be watched for changes

Why?

Because angular-ui-router is too bloody complicated, difficult to grok and the documentation sucks.

This project is an attempt to simplify routing to its absolute bare essentials using a sane syntax with sensible examples.

Installation

The following instructions detail how to get MFDC-Router working for an Angular setup:

  1. Grab the NPM
npm install --save @momsfriendlydevco/router
  1. Install the required script somewhere in your build chain or include it in a HTML header:
<script src="/libs/@momsfriendlydevco/router/dist/angular-mfdc-router.min.js"/>
  1. Include the router in your main angular.module() call:
var app = angular.module('app', ['angular-mfdc-router'])
  1. Start using the router by defining paths:
app
	.run($router => $router.when('/foo').component('fooCtrl'))
	.component('fooCtrl', {
		// ... //
	})

Common usage

// Use a given controller when matching a path (ID will be available in $router.params.id)
$router.when('/widgets/:id').component('widgetsListCtrl');

// Specify that some parameters are optional - just suffix each token with a question mark
$router.when('/foo/:id1?/:id2?/:id3?').component('fooCtrl');

// When visiting one URL redirect to another
$router.when('/foo').go('/bar');
// OR
$router.when('/foo').redirect('/bar');

// Set the priority of a routing rule
$router.when('/foo').priority('low').redirect('/somewhere/else');

// Require that a rule only matches if a promise resolves correctly (we assume $session.isLoggedIn works)
$router.when('/super-secure-area').requires($session.isLoggedIn).component('superSecureCtrl');

// ... or multiple promises
$router.when('/super-secure-area')
	.require(something)
	.require(somethingElse)
	.require([lots, o, promises])
	.component('superSecureCtrl');


// Listen for routing events and perform an action when the page has changed

// Before we navigate...
$rootScope.$on('$routerStart', ()=> /* Do something */)

// After we navigated
$rootScope.$on('$routerSuccess', ()=> /* Do something */)

// After we navigated and something went wrong
$rootScope.$on('$routerError', ()=> /* Do something */)

API

$router (service)

A global Angular service. This can be required anywhere in your project and always exposes the same data.

$router.routes

An array in priority order of all currently configured rules.

$router.path

The current path portion of the route.

$router.params

An object containing all parameters extracted from the URL in tokenized form.

For example if the rule has the path /widgets/:id and the current URL is /widgets/123 the parameters object will be {id: 123}.

NOTE: This object will never break its reference meaning it can be watched and rebound.

$router.query

An object containing all query parameters extracted from the URL.

For example if the rule has the path /widgets/:id and the current URL is /widgets/123?foo=bar&baz the parameters object will be {foo: 'bar', baz: true}.

NOTE: This object will never break its reference meaning it can be watched and rebound.

$router.setQuery(key, [val])

Set the query string and force a re-evaluate operation.

This function can be called in a variety of ways:

  • If key is empty or equal to {} the entire query is removed
  • If key is an object the new object will completely overwrite any existing query values
  • If key is a string and val is set, just one query portion will be set
  • If key is a string and val is undefined (or omitted) the given query key will be removed.
// Set the entire query string (i.e. remove ALL queries excpect the ones given in the object)
$router.setQuery({foo: 'foo!'})

// Set just the 'bar' component of the query (i.e. if anything else exists leave it as is)
$router.setQuery('bar', 'bar!')

// Remove the 'baz' component if its is set, leaving everything else in place
$router.setQuery('baz', undefined);
// OR
$router.setQuery('baz');

$router.tokenRule(token, validator)

Define a rule to be used with a given token. This should be a function that will return whether the given value can be accepted to satisfy that rule segment

// Any URL containing ':id' will only validate if ID is a number
$router.tokenRule(':id', v => /^0-9+$/.test(v));

// Any URL containing ':id' will only validate if ID is a MongoDB style ObjectID
$router.tokenRule(':id', v => /^[0-9a-f]+$/.test(v));

// Only allow ISO style dates (year-month-day)
$router.tokenRule(':date', v => /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(v));

$router.current.main

The currently matched rule.

$router.priorityAliases

A lookup object of different priority aliases - e.g. lowest, normal etc.

$router.sort

Various configuration options to sort the $router.routes collection. This contains $router.sort.enabled which toggles whether to sort, $router.sort.isSorted which specifies the dirty flag of the routes being sorted, $router.sort.keyOrder which is a complex collection of how to sort the array (see the source code). The sorting function can be overridden by subclassing / decorating $router.sort.sorter.

$router.warns

An object containing a list of keys corresponding to various functions the router will check before it runs. These should only ever be enabled at run time as they take extra processing cycles.

Use $router.warnings(KEY, ENABLED) to set by key or disable them all with $router.warnings(false) to disable all.

$router.pathToRegExp()

Utility function to convert a path into a regular expression.

$router.rule(path)

Create a new router rule and append it onto the $router.routes stack.

See RouterRule.

$router.warnings(key, value) / $router.warnings(false)

Disable a specific warning / debugging flag within the router or disable all flags with $router.warnings(false).

$router.when(path)

Alias of $router.rule(path).

$router.resolve(path)

Return the first matching router rule that matches the given path.

$router.go(path) / $router.redirect(path)

Navigate to the given path.

RouterRule (Object instance)

An instance of a router rule. If a path is specified RouterRule.path() is automatically called.

RouterRule.data(object | key,value)

Store data in the routers rule. This is a handy area to stash information about a route such as the page title. If passed a single entity the entire RouterRule._data element is overwritten, otherwise this function is treated like a key setter.

RouterRule.component([id='main'], componentName)

Configure the action of the rule to display the named component.

If id is omitted "main" is assumed (i.e. you only have one <router-view></router-view> somewhere in your template.

To use single or multiple views you can use any of the following component setting styles:

// Sets only the 'main' router-view
RouterRule.component('fooCtrl')

// Same as above
RouterRule.component('main', 'fooCtrl')

// Set the main router-view to the 'fooCtrl' component and the `<router-view route-id="aside"></router-view>` to the 'barCtrl' component
RouterRule
	.component('main', 'fooCtrl')
	.component('aside', 'barCtrl')

// Same as above but using an object
RouterRule
	.component({main: 'fooCtrl', aside: 'barCtrl')

RouterRule.go(path) / RouterRule.redirect(path)

Configure the action of the rule to redirect to another path

RouterRule.matches(path)

Tests a given path against the rule. This will return a boolean if the rule matches.

RouterRule.params(Object|id, [value])

Set additional parameters be to populated into $router.params if this rule matches. If value is a function it is executed when the rule matches and its return value used to populate the parameter. This can be useful if you need to perform some calculation before passing the parameter downstream.

RouterRule.path(path)

Set the path of the rule. This can be a tokenized Ruby style path or a regular expression.

RouterRule.priorty(priority)

Set the priority out of 100 that the rule should install itself at in the router rules stack. The value can either be a number or a string corresponding to an entry in $router.priorityAliases.

RouterRule.requires(...tests) / RouterRule.require(...tests)

A function, promise or an array therefof of conditions that must be satisfied for this rule to match.

// Only match if `$session.promise()` returns correctly
$router.when('/foo')
	.requires($session.promise)
	.component('fooCtrl');

NOTE: Promises only ever resolve once so its important to pass in a function which creates a new promise (a Promise Factory pattern). This was the factory is instanciated whenever the rule is checked with a new state each time. The Router will warn if it is passed a Promise directly rather than a function which returns a promise.

If you pass in a non-factory function the return value must be a boolean true or false to map onto Promise.resolve() / Promise.reject() respectively. All other return values (strings, objects etc.) will be passed to Promise.all() to be resolved which generally doesnt do what you want it to. The function is always called as (path, rule).

RouterRule.title(title)

A shorthand function to set the title element of RouterRule._data. In effect this calls RouterRule.data('title', YOUR VALUE) for you.

RouterRule.template([id], html)

Configure the action of the rule to display the given HTML string.

RouterRule.templateUrl([id], url)

Configure the action of the rule to display the given HTML string fetch from either the $templateCache object or via $http.

NOTE: Can also be called as templateURL()

RouterRule.extractParams(path)

Function to extract parameters from the URL into the parameters object.

routerView (directive)

Used in the main page template to indicate where to place the page content.

<router-view></router-view>

Router view can also take the following options:

Option Type Default Description
route-id String "main" The ID of the route to react to. Use RouterRule.component() to define this in the rule

Events

The following events are broadcast globally and can be trapped by hooking into the $rootScope listener:

$rootScope.$on('$routerStart', ()=> console.log('Routing has started!'));

Generally the event progression when moving from rule Foo to rule Bar is:

  • $routerStart(FooRule)
  • $routerStartResolved(BarRule, FooRule)
  • $routerSuccess(FooRule, 'main')

if any error occurs $routerError(error) will be emitted.

if you are using one of the '*-debug' scripts $routerDebug(message) will also be emitted.

$routerStart

Called as (currentRule) when a routing operation starts. Note that currentRule is the rule we are replacing and we dont yet know the new rule.

$routerStartResolved

Called as (newRule, oldRule) when routing after we have resolved which rule we are moving to.

$routerSuccess

Called as (newRule, [id]) when a routing operation completes. ID is the invididual router component that has changed (i.e. multiple routers on the same page). This is invoked when controllers, redirects or other actions have completed.

$routerError

Called as (err) when an error occurs.

Readme

Keywords

Package Sidebar

Install

npm i @momsfriendlydevco/router

Weekly Downloads

1

Version

1.1.10

License

MIT

Unpacked Size

159 kB

Total Files

12

Last publish

Collaborators

  • mr-yellow
  • melromero
  • hash-bang
  • 1watt
  • eekthecat