Skip to content

Commit 32e64f0

Browse files
feat(LocationServices): Add a parts() method which returns the URL parts as an object
refactor(UrlSync): Consolidate UrlSync UrlListen UrlDeferIntercept feat(UrlServices): Add `match()`: given a URL, return the best matching Url Rule
1 parent ee340d4 commit 32e64f0

File tree

6 files changed

+103
-38
lines changed

6 files changed

+103
-38
lines changed

src/common/coreservices.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/** for typedoc */
88
import {IInjectable, Obj} from "./common";
99
import { Disposable } from "../interface";
10+
import { UrlParts } from "../url/interface";
1011

1112
export let notImplemented = (fnname: string) => () => {
1213
throw new Error(`${fnname}(): No coreservices implementation for UI-Router is loaded.`);
@@ -77,29 +78,32 @@ export interface LocationServices extends Disposable {
7778
url(newurl: string, replace?: boolean, state?: any): string;
7879

7980
/**
80-
* Gets the path portion of the current url
81+
* Gets the path part of the current url
8182
*
8283
* If the current URL is `/some/path?query=value#anchor`, this returns `/some/path`
8384
*
8485
* @return the path portion of the url
8586
*/
8687
path(): string;
88+
8789
/**
88-
* Gets the search portion of the current url as an object
90+
* Gets the search part of the current url as an object
8991
*
9092
* If the current URL is `/some/path?query=value#anchor`, this returns `{ query: 'value' }`
9193
*
9294
* @return the search (querystring) portion of the url, as an object
9395
*/
9496
search(): { [key: string]: any };
97+
9598
/**
96-
* Gets the hash portion of the current url
99+
* Gets the hash part of the current url
97100
*
98101
* If the current URL is `/some/path?query=value#anchor`, this returns `anchor`
99102
*
100103
* @return the hash (anchor) portion of the url
101104
*/
102105
hash(): string;
106+
103107
/**
104108
* Registers a url change handler
105109
*

src/url/interface.ts

+18-12
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ export interface UrlMatcherConfig {
9191
*/
9292
defaultSquashPolicy(value?: (boolean|string)): (boolean|string);
9393

94-
9594
/**
9695
* Creates and registers a custom [[ParamTypeDefinition]] object
9796
*
@@ -120,7 +119,7 @@ export interface UrlMatcherConfig {
120119
}
121120

122121
/** @internalapi */
123-
export interface UrlSync {
122+
export interface UrlSyncApi {
124123
/**
125124
* Checks the URL for a matching [[UrlRule]]
126125
*
@@ -142,10 +141,7 @@ export interface UrlSync {
142141
* ```
143142
*/
144143
sync(evt?): void;
145-
}
146144

147-
/** @internalapi */
148-
export interface UrlListen {
149145
/**
150146
* Starts or stops listening for URL changes
151147
*
@@ -166,11 +162,8 @@ export interface UrlListen {
166162
* });
167163
* ```
168164
*/
169-
listen(enabled?: boolean): Function;
170-
}
165+
listen(enabled?: boolean): Function
171166

172-
/** @internalapi */
173-
export interface UrlDeferIntercept {
174167
/**
175168
* Disables monitoring of the URL.
176169
*
@@ -195,7 +188,7 @@ export interface UrlDeferIntercept {
195188
* @param defer Indicates whether to defer location change interception.
196189
* Passing no parameter is equivalent to `true`.
197190
*/
198-
deferIntercept(defer?: boolean);
191+
deferIntercept(defer?: boolean)
199192
}
200193

201194
/**
@@ -376,10 +369,23 @@ export interface UrlRules {
376369
*/
377370
export interface UrlParts {
378371
path: string;
379-
search: { [key: string]: any };
380-
hash: string;
372+
search?: { [key: string]: any };
373+
hash?: string;
381374
}
382375

376+
/**
377+
* A UrlRule match result
378+
*
379+
* The result of UrlRouter.match()
380+
*/
381+
export interface MatchResult {
382+
/** The matched value from a [[UrlRule]] */
383+
match: any;
384+
/** The rule that matched */
385+
rule: UrlRule;
386+
/** The match result weight */
387+
weight: number;
388+
}
383389
/**
384390
* A function that matches the URL for a [[UrlRule]]
385391
*

src/url/urlRouter.ts

+31-18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/**
22
* @internalapi
33
* @module url
4-
*/ /** for typedoc */
5-
import { removeFrom, createProxyFunctions, inArray, composeSort, sortBy } from "../common/common";
4+
*/
5+
/** for typedoc */
6+
import { removeFrom, createProxyFunctions, inArray, composeSort, sortBy, extend } from "../common/common";
67
import { isFunction, isString, isDefined } from "../common/predicates";
78
import { UrlMatcher } from "./urlMatcher";
89
import { RawParams } from "../params/interface";
@@ -11,7 +12,7 @@ import { UIRouter } from "../router";
1112
import { val, is, pattern, prop, pipe } from "../common/hof";
1213
import { UrlRuleFactory } from "./urlRule";
1314
import { TargetState } from "../state/targetState";
14-
import { UrlRule, UrlRuleHandlerFn, UrlParts, UrlRules, UrlSync, UrlListen, UrlDeferIntercept } from "./interface";
15+
import { UrlRule, UrlRuleHandlerFn, UrlParts, UrlRules, UrlSyncApi, MatchResult } from "./interface";
1516
import { TargetStateDef } from "../state/interface";
1617

1718
/** @hidden */
@@ -53,7 +54,7 @@ defaultRuleSortFn = composeSort(
5354
* This class updates the URL when the state changes.
5455
* It also responds to changes in the URL.
5556
*/
56-
export class UrlRouter implements UrlRules, UrlSync, UrlListen, UrlDeferIntercept, Disposable {
57+
export class UrlRouter implements UrlRules, UrlSyncApi, Disposable {
5758
/** used to create [[UrlRule]] objects for common cases */
5859
public urlRuleFactory: UrlRuleFactory;
5960

@@ -85,25 +86,20 @@ export class UrlRouter implements UrlRules, UrlSync, UrlListen, UrlDeferIntercep
8586
this._rules.sort(this._sortFn = compareFn || this._sortFn);
8687
}
8788

88-
/** @inheritdoc */
89-
sync(evt?) {
90-
if (evt && evt.defaultPrevented) return;
91-
92-
let router = this._router,
93-
$url = router.urlService,
94-
$state = router.stateService;
95-
89+
/**
90+
* Given a URL, check all rules and return the best [[MatchResult]]
91+
* @param url
92+
* @returns {MatchResult}
93+
*/
94+
match(url: UrlParts): MatchResult {
95+
url = extend({path: '', search: {}, hash: '' }, url);
9696
let rules = this.rules();
9797
if (this._otherwiseFn) rules.push(this._otherwiseFn);
9898

99-
let url: UrlParts = {
100-
path: $url.path(), search: $url.search(), hash: $url.hash()
101-
};
102-
10399
// Checks a single rule. Returns { rule: rule, match: match, weight: weight } if it matched, or undefined
104-
interface MatchResult { match: any, rule: UrlRule, weight: number }
100+
105101
let checkRule = (rule: UrlRule): MatchResult => {
106-
let match = rule.match(url, router);
102+
let match = rule.match(url, this._router);
107103
return match && { match, rule, weight: rule.matchPriority(match) };
108104
};
109105

@@ -121,6 +117,23 @@ export class UrlRouter implements UrlRules, UrlSync, UrlListen, UrlDeferIntercep
121117
best = (!best || current && current.weight > best.weight) ? current : best;
122118
}
123119

120+
return best;
121+
}
122+
123+
/** @inheritdoc */
124+
sync(evt?) {
125+
if (evt && evt.defaultPrevented) return;
126+
127+
let router = this._router,
128+
$url = router.urlService,
129+
$state = router.stateService;
130+
131+
let url: UrlParts = {
132+
path: $url.path(), search: $url.search(), hash: $url.hash()
133+
};
134+
135+
let best = this.match(url);
136+
124137
let applyResult = pattern([
125138
[isString, (newurl: string) => $url.url(newurl)],
126139
[TargetState.isDef, (def: TargetStateDef) => $state.go(def.state, def.params, def.options)],

src/url/urlService.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { UIRouter } from "../router";
77
import { LocationServices, notImplemented, LocationConfig } from "../common/coreservices";
88
import { noop, createProxyFunctions } from "../common/common";
9-
import { UrlConfig, UrlSync, UrlListen, UrlRules, UrlDeferIntercept } from "./interface";
9+
import { UrlConfig, UrlSyncApi, UrlRules, UrlParts, MatchResult } from "./interface";
1010

1111
/** @hidden */
1212
const makeStub = (keys: string[]): any =>
@@ -16,12 +16,12 @@ const makeStub = (keys: string[]): any =>
1616
/** @hidden */ const locationConfigFns = ["port", "protocol", "host", "baseHref", "html5Mode", "hashPrefix"];
1717
/** @hidden */ const umfFns = ["type", "caseInsensitive", "strictMode", "defaultSquashPolicy"];
1818
/** @hidden */ const rulesFns = ["sort", "when", "otherwise", "rules", "rule", "removeRule"];
19-
/** @hidden */ const syncFns = ["deferIntercept", "listen", "sync"];
19+
/** @hidden */ const syncFns = ["deferIntercept", "listen", "sync", "match"];
2020

2121
/**
2222
* API for URL management
2323
*/
24-
export class UrlService implements LocationServices, UrlSync, UrlListen, UrlDeferIntercept {
24+
export class UrlService implements LocationServices, UrlSyncApi {
2525
/** @hidden */
2626
static locationServiceStub: LocationServices = makeStub(locationServicesFns);
2727
/** @hidden */
@@ -41,6 +41,18 @@ export class UrlService implements LocationServices, UrlSync, UrlListen, UrlDefe
4141
/** @inheritdoc */
4242
onChange(callback: Function): Function { return };
4343

44+
45+
/**
46+
* Returns the current URL parts
47+
*
48+
* This method returns the current URL components as a [[UrlParts]] object.
49+
*
50+
* @returns the current url parts
51+
*/
52+
parts(): UrlParts {
53+
return { path: this.path(), search: this.search(), hash: this.hash() }
54+
}
55+
4456
dispose() { }
4557

4658
/** @inheritdoc */
@@ -49,6 +61,8 @@ export class UrlService implements LocationServices, UrlSync, UrlListen, UrlDefe
4961
listen(enabled?: boolean): Function { return };
5062
/** @inheritdoc */
5163
deferIntercept(defer?: boolean) { return }
64+
/** @inheritdoc */
65+
match(urlParts: UrlParts): MatchResult { return }
5266

5367
/**
5468
* A nested API for managing URL rules and rewrites

src/vanilla/hashLocation.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class HashLocationService implements LocationServices, Disposable {
2222
url(url?: string, replace: boolean = true): string {
2323
if (isDefined(url)) location.hash = url;
2424
return buildUrl(this);
25-
};
25+
}
2626

2727
onChange(cb: EventListener) {
2828
window.addEventListener('hashchange', cb, false);

test/urlRouterSpec.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { LocationServices } from "../src/common/coreservices";
44
import { UrlService } from "../src/url/urlService";
55
import { StateRegistry } from "../src/state/stateRegistry";
66
import { noop } from "../src/common/common";
7-
import { UrlRule } from "../src/url/interface";
7+
import { UrlRule, MatchResult } from "../src/url/interface";
88

99
declare var jasmine;
1010
var _anything = jasmine.anything();
@@ -272,6 +272,34 @@ describe("UrlRouter", function () {
272272
})
273273
});
274274
});
275+
276+
describe('match', () => {
277+
let A, B, CCC;
278+
beforeEach(() => {
279+
A = stateRegistry.register({ name: 'A', url: '/:pA' });
280+
B = stateRegistry.register({ name: 'B', url: '/BBB' });
281+
CCC = urlService.rules.when('/CCC', '/DDD');
282+
});
283+
284+
it("should return the best match for a URL 1", () => {
285+
let match: MatchResult = urlRouter.match({ path: '/BBB' });
286+
expect(match.rule.type).toBe("STATE");
287+
expect(match.rule['state']).toBe(B)
288+
});
289+
290+
it("should return the best match for a URL 2", () => {
291+
let match: MatchResult = urlRouter.match({ path: '/EEE' });
292+
expect(match.rule.type).toBe("STATE");
293+
expect(match.rule['state']).toBe(A);
294+
expect(match.match).toEqual({ pA: 'EEE' });
295+
});
296+
297+
it("should return the best match for a URL 3", () => {
298+
let match: MatchResult = urlRouter.match({ path: '/CCC' });
299+
expect(match.rule.type).toBe("URLMATCHER");
300+
expect(match.rule).toBe(CCC);
301+
});
302+
});
275303
});
276304

277305
describe('UrlRouter.deferIntercept', () => {

0 commit comments

Comments
 (0)