Skip to content

Commit 584a255

Browse files
refactor(*): Create a Router class for bootstrapping ui-router.
refactor(*): Create a coreservices API paving the way for ng1 or ng2 module plugins refactor(*): externalize some ng1 services and injector registrations
1 parent cf4e5f9 commit 584a255

File tree

11 files changed

+154
-62
lines changed

11 files changed

+154
-62
lines changed

src/common/angular1.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
/** @module common */ /** for typedoc */
2+
3+
/**
4+
* Provides the implementation for services defined in [[coreservices]], and registers some
5+
* with the angular 1 injector.
6+
*/
7+
28
/// <reference path='../../typings/angularjs/angular.d.ts' />
39
import {IQService} from "angular";
410
import {Router} from "../router";
11+
import {services} from "./coreservices";
12+
import {isObject} from "./common";
513

614
let app = angular.module("ui.router.angular1", []);
715

@@ -60,11 +68,42 @@ function runBlock($injector) {
6068

6169
app.run(runBlock);
6270

71+
const bindFunctions = (fnNames: string[], from, to) =>
72+
fnNames.forEach(name => to[name] = from[name].bind(from));
6373

64-
let router = new Router();
74+
let router = null;
6575

76+
ng1UIRouter.$inject = ['$locationProvider'];
77+
function ng1UIRouter($locationProvider) {
78+
router = new Router();
79+
bindFunctions(['hashPrefix'], $locationProvider, services.location);
80+
81+
this.$get = $get;
82+
$get.$inject = ['$location', '$browser', '$sniffer'];
83+
function $get($location, $browser, $sniffer) {
84+
85+
services.location.html5Mode = function() {
86+
var html5Mode = $locationProvider.html5Mode();
87+
html5Mode = isObject(html5Mode) ? html5Mode.enabled : html5Mode;
88+
return html5Mode && $sniffer.history;
89+
};
90+
91+
var $locationFnNames = ['hash', 'path', 'replace', 'search', 'url', 'port', 'protocol', 'host'];
92+
bindFunctions($locationFnNames, $location, services.location);
93+
bindFunctions(['baseHref'], $browser, services.location);
94+
95+
return router;
96+
}
97+
}
98+
99+
angular.module('ui.router.init', ['ng']).provider("ng1UIRouter", <any> ng1UIRouter);
66100
// Register as a provider so it's available to other providers
67-
angular.module('ui.router.util').provider('$urlMatcherFactory', () => router.urlMatcherFactory);
101+
angular.module('ui.router.util').provider('$urlMatcherFactory', ['ng1UIRouterProvider', () => router.urlMatcherFactory]);
102+
angular.module('ui.router.router').provider('$urlRouter', ['ng1UIRouterProvider', () => router.urlRouterProvider]);
103+
angular.module('ui.router.state').provider('$state', ['ng1UIRouterProvider', () => router.stateProvider]);
104+
105+
/* This effectively calls $get() to init when we enter runtime */
106+
angular.module('ui.router.state').run(['ng1UIRouter', function(ng1UIRouter) { }]);
107+
angular.module('ui.router.state').run(['$state', function($state) { }]);
68108
angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
69109

70-
export { router };

src/common/common.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ export function tail(collection: any[]): any {
411411
* in your angular app (use {@link ui.router} module instead).
412412
*
413413
*/
414-
angular.module('ui.router.util', ['ng']);
414+
angular.module('ui.router.util', ['ng', 'ui.router.init']);
415415

416416
/**
417417
* @ngdoc overview
@@ -479,6 +479,6 @@ angular.module('ui.router.state', ['ui.router.router', 'ui.router.util', 'ui.rou
479479
* </html>
480480
* </pre>
481481
*/
482-
angular.module('ui.router', ['ui.router.state', 'ui.router.angular1']);
482+
angular.module('ui.router', ['ui.router.init', 'ui.router.state', 'ui.router.angular1']);
483483

484484
angular.module('ui.router.compat', ['ui.router']);

src/common/coreservices.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as foo from "./common";
2+
3+
/**
4+
* Services related to the browser location (url)
5+
*/
6+
interface LocationServices {
7+
replace(): void;
8+
url(newurl: string): string;
9+
url(): string;
10+
path(): string;
11+
search(): string;
12+
hash(): string;
13+
14+
port(): number;
15+
protocol(): string;
16+
host(): string;
17+
18+
baseHref(): string;
19+
html5Mode(): boolean;
20+
hashPrefix(): string;
21+
hashPrefix(newprefix: string): string;
22+
}
23+
24+
interface Services {
25+
location: LocationServices;
26+
}
27+
28+
let services: Services = {
29+
location: <any> {}
30+
};
31+
32+
let notImplemented = (fnname) => () => {
33+
throw new Error(`${fnname}(): No coreservices implementation for UI-Router is loaded. You should include one of: ['angular1.js']`);
34+
};
35+
36+
[
37+
"replace",
38+
"url",
39+
"path",
40+
"search",
41+
"hash",
42+
"port",
43+
"protocol",
44+
"host",
45+
"baseHref",
46+
"html5Mode",
47+
"hashPrefix"
48+
].forEach(key => services.location[key] = notImplemented(key));
49+
50+
export {services};

src/common/module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/** @module common */ /** for typedoc */
22
export * from "./angular1";
33
export * from "./common";
4+
export * from "./coreservices";
45
export * from "./queue";
56
export * from "./trace";

src/router.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import {UrlMatcherFactory} from "./url/urlMatcherFactory";
2+
import {$UrlRouterProvider} from "./url/urlRouter";
3+
import {$StateProvider} from "./state/state";
24

35
class Router {
4-
constructor() {}
56
urlMatcherFactory: UrlMatcherFactory = new UrlMatcherFactory();
7+
urlRouterProvider = new $UrlRouterProvider(this.urlMatcherFactory);
8+
stateProvider = new $StateProvider(this.urlRouterProvider, this.urlMatcherFactory);
9+
10+
constructor() {
11+
12+
}
613
}
714

815
export { Router };

src/state/state.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
extend, defaults, copy, equalForKeys, forEach, find, prop,
44
propEq, ancestors, noop, isDefined, isObject, isString, values
55
} from "../common/common";
6-
import {Queue} from "../common/module";
6+
import {Queue} from "../common/queue";
77

88
import {IServiceProviderFactory, IPromise} from "angular";
99
import {StateService, StateDeclaration, StateOrName, HrefOptions, ViewDeclaration } from "./interface";
@@ -17,6 +17,7 @@ import {TransitionManager} from "./hooks/transitionManager";
1717
import {paramTypes, Param, Type, StateParams} from "../params/module";
1818
import {UrlMatcher} from "../url/urlMatcher";
1919
import {ViewConfig} from "../view/view";
20+
import {UrlMatcherFactory} from "../url/urlMatcherFactory";
2021

2122
/**
2223
* @ngdoc object
@@ -39,8 +40,7 @@ import {ViewConfig} from "../view/view";
3940
*
4041
* The `$stateProvider` provides interfaces to declare these states for your app.
4142
*/
42-
$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
43-
function $StateProvider( $urlRouterProvider, $urlMatcherFactoryProvider) {
43+
export function $StateProvider($urlRouterProvider, $urlMatcherFactoryProvider: UrlMatcherFactory) {
4444

4545
let root: State, states: { [key: string]: State } = {};
4646
let $state: StateService = <any> function $state() {};
@@ -795,7 +795,3 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactoryProvider) {
795795
return $state;
796796
}
797797
}
798-
799-
angular.module('ui.router.state')
800-
.provider('$state', <IServiceProviderFactory> $StateProvider)
801-
.run(['$state', function($state) { /* This effectively calls $get() to init when we enter runtime */ }]);

src/state/stateBuilder.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {StateDeclaration} from "./interface";
44

55
import {State, StateMatcher} from "./module";
66
import {Param} from "../params/module";
7+
import {UrlMatcherFactory} from "../url/urlMatcherFactory";
8+
import {UrlMatcher} from "../url/urlMatcher";
79

810
const parseUrl = (url: string): any => {
911
if (!isString(url)) return false;
@@ -42,7 +44,7 @@ export class StateBuilder {
4244
/** An object that contains all the BuilderFunctions registered, key'd by the name of the State property they build */
4345
private builders: Builders;
4446

45-
constructor(root: () => State, private matcher: StateMatcher, $urlMatcherFactoryProvider) {
47+
constructor(root: () => State, private matcher: StateMatcher, $urlMatcherFactoryProvider: UrlMatcherFactory) {
4648
let self = this;
4749

4850
this.builders = {
@@ -72,7 +74,7 @@ export class StateBuilder {
7274

7375
if (!url) return null;
7476
if (!$urlMatcherFactoryProvider.isMatcher(url)) throw new Error(`Invalid url '${url}' in state '${state}'`);
75-
return (parsed && parsed.root) ? url : ((parent && parent.navigable) || root()).url.append(url);
77+
return (parsed && parsed.root) ? url : ((parent && parent.navigable) || root()).url.append(<UrlMatcher> url);
7678
}],
7779

7880
// Keep track of the closest ancestor state that has a URL (i.e. is navigable)

src/url/urlMatcherFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class UrlMatcherFactory {
6666
* @param config The config object hash.
6767
* @returns The UrlMatcher.
6868
*/
69-
compile(pattern: string, config: { [key: string]: any }) {
69+
compile(pattern: string, config?: { [key: string]: any }) {
7070
return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
7171
}
7272

src/url/urlRouter.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import {isFunction, isString, isDefined, isArray, isObject, extend} from "../common/common";
44
import {IServiceProviderFactory} from "angular";
55
import {UrlMatcher} from "./module";
6+
import {services} from "../common/coreservices";
7+
import {UrlMatcherFactory} from "./urlMatcherFactory";
8+
import {runtime} from "../common/angular1";
9+
10+
let $location = services.location;
611

712
/**
813
* @ngdoc object
@@ -20,8 +25,7 @@ import {UrlMatcher} from "./module";
2025
* There are several methods on `$urlRouterProvider` that make it useful to use directly
2126
* in your module config.
2227
*/
23-
$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
24-
function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
28+
export function $UrlRouterProvider($urlMatcherFactory: UrlMatcherFactory) {
2529
var rules = [], otherwise = null, interceptDeferred = false, listener;
2630

2731
// Returns a string that is a prefix of all strings matching the RegExp
@@ -172,8 +176,8 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
172176
redirect = $urlMatcherFactory.compile(handler);
173177
handler = ['$match', redirect.format.bind(redirect)];
174178
}
175-
return extend(function ($injector, $location) {
176-
return handleIfMatch($injector, handler, what.exec($location.path(), $location.search(), $location.hash()));
179+
return extend(function () {
180+
return handleIfMatch(runtime.$injector, handler, what.exec($location.path(), $location.search(), $location.hash()));
177181
}, {
178182
prefix: isString(what.prefix) ? what.prefix : ''
179183
});
@@ -185,8 +189,8 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
185189
redirect = handler;
186190
handler = ['$match', ($match) => interpolate(redirect, $match)];
187191
}
188-
return extend(function ($injector, $location) {
189-
return handleIfMatch($injector, handler, what.exec($location.path()));
192+
return extend(function () {
193+
return handleIfMatch(runtime.$injector, handler, what.exec($location.path()));
190194
}, {
191195
prefix: regExpPrefix(what)
192196
});
@@ -262,22 +266,17 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
262266
* @ngdoc object
263267
* @name ui.router.router.$urlRouter
264268
*
265-
* @requires $location
266-
* @requires $rootScope
267-
* @requires $injector
268-
* @requires $browser
269-
*
270269
* @description
271270
*
272271
*/
273272
this.$get = $get;
274-
$get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];
275-
function $get( $location, $rootScope, $injector, $browser, $sniffer) {
273+
$get.$inject = [ '$rootScope'];
274+
function $get( $rootScope) {
276275

277276
var location = $location.url();
278277

279278
function appendBasePath(url, isHtml5, absolute) {
280-
var baseHref = $browser.baseHref();
279+
var baseHref = $location.baseHref();
281280
if (baseHref === '/') return url;
282281
if (isHtml5) return baseHref.slice(0, -1) + url;
283282
if (absolute) return baseHref.slice(1) + url;
@@ -289,10 +288,13 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
289288
if (evt && evt.defaultPrevented) return;
290289

291290
function check(rule) {
292-
var handled = rule($injector, $location);
291+
var handled = rule(runtime.$injector, $location);
293292

294293
if (!handled) return false;
295-
if (isString(handled)) $location.replace().url(handled);
294+
if (isString(handled)) {
295+
$location.replace();
296+
$location.url(handled);
297+
}
296298
return true;
297299
}
298300
var n = rules.length, i;
@@ -390,18 +392,12 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
390392
href(urlMatcher: UrlMatcher, params: any, options: any): string {
391393
if (!urlMatcher.validates(params)) return null;
392394

393-
var isHtml5 = $locationProvider.html5Mode();
394-
if (isObject(isHtml5)) {
395-
isHtml5 = isHtml5.enabled;
396-
}
397-
398-
isHtml5 = isHtml5 && $sniffer.history;
399-
400395
var url = urlMatcher.format(params);
401396
options = options || {};
402397

398+
var isHtml5 = services.location.html5Mode();
403399
if (!isHtml5 && url !== null) {
404-
url = "#" + $locationProvider.hashPrefix() + url;
400+
url = "#" + $location.hashPrefix() + url;
405401
}
406402
url = appendBasePath(url, isHtml5, options.absolute);
407403

@@ -410,12 +406,11 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
410406
}
411407

412408
var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
413-
port = (port === 80 || port === 443 ? '' : ':' + port);
409+
port = <any> (port === 80 || port === 443 ? '' : ':' + port);
414410

415411
return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
416412
}
417413
};
418414
}
419415
}
420416

421-
angular.module('ui.router.router').provider('$urlRouter', <IServiceProviderFactory> $UrlRouterProvider);

test/stateSpec.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ var module = angular.mock.module;
22
var uiRouter = require("ui-router");
33
var common = uiRouter.common;
44
var RejectType = uiRouter.transition.RejectType;
5-
var extend = common.extend,
6-
forEach = common.forEach;
5+
var extend = common.extend;
6+
var forEach = common.forEach;
7+
var services = common.services;
78
var state = uiRouter.state;
89
var StateMatcher = state.StateMatcher;
910
var StateBuilder = uiRouter.state.StateBuilder;
@@ -1081,7 +1082,7 @@ describe('state', function () {
10811082

10821083
describe('when $browser.baseHref() exists', function() {
10831084
beforeEach(inject(function($browser) {
1084-
spyOn($browser, 'baseHref').and.callFake(function() {
1085+
spyOn(services.location, 'baseHref').and.callFake(function() {
10851086
return '/base/';
10861087
});
10871088
}));
@@ -1417,22 +1418,22 @@ describe('state', function () {
14171418

14181419
it('should replace browser history when "replace" enabled', inject(function ($state, $rootScope, $location, $q) {
14191420

1420-
spyOn($location, 'replace');
1421+
spyOn(services.location, 'replace');
14211422

14221423
$state.transitionTo('about', {}, { location: 'replace' });
14231424
$q.flush();
14241425

1425-
expect($location.replace).toHaveBeenCalled();
1426+
expect(services.location.replace).toHaveBeenCalled();
14261427
}));
14271428

14281429
it('should not replace history normally', inject(function ($state, $rootScope, $location, $q) {
14291430

1430-
spyOn($location, 'replace');
1431+
spyOn(services.location, 'replace');
14311432

14321433
$state.transitionTo('about');
14331434
$q.flush();
14341435

1435-
expect($location.replace).not.toHaveBeenCalled();
1436+
expect(services.location.replace).not.toHaveBeenCalled();
14361437

14371438
}));
14381439
});

0 commit comments

Comments
 (0)