Skip to content

Commit 0f8efce

Browse files
fix(angular1_router): support link generation with custom hashPrefixes
1 parent 69c1405 commit 0f8efce

File tree

3 files changed

+127
-107
lines changed

3 files changed

+127
-107
lines changed

modules/angular1_router/lib/facades.es5

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,5 @@ Location.prototype.prepareExternalUrl = function(url) {
320320
if (url.length > 0 && !url.startsWith('/')) {
321321
url = '/' + url;
322322
}
323-
if(!$location.$$html5) {
324-
return '#' + url;
325-
} else {
326-
return '.' + url;
327-
}
323+
return $location.$$html5 ? '.' + url : '#' + $locationHashPrefix + url;
328324
};

modules/angular1_router/src/module_template.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,33 @@ angular.module('ngComponentRouter').
44
// Because Angular 1 has no notion of a root component, we use an object with unique identity
55
// to represent this. Can be overloaded with a component name
66
value('$routerRootComponent', new Object()).
7-
factory('$rootRouter', ['$q', '$location', '$browser', '$rootScope', '$injector', '$routerRootComponent', routerFactory]);
87

9-
function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRootComponent) {
8+
// Unfortunately, $location doesn't expose what the current hashPrefix is
9+
// So we have to monkey patch the $locationProvider to capture this value
10+
provider('$locationHashPrefix', ['$locationProvider', $locationHashPrefixProvider]).
11+
factory('$rootRouter', ['$q', '$location', '$browser', '$rootScope', '$injector', '$routerRootComponent', '$locationHashPrefix', routerFactory]);
12+
13+
function $locationHashPrefixProvider($locationProvider) {
14+
15+
// Get hold of the original hashPrefix method
16+
var hashPrefixFn = $locationProvider.hashPrefix.bind($locationProvider);
17+
18+
// Read the current hashPrefix (in case it was set before this monkey-patch occurred)
19+
var hashPrefix = hashPrefixFn();
20+
21+
// Override the helper so that we can read any changes to the prefix (after this monkey-patch)
22+
$locationProvider.hashPrefix = function(prefix) {
23+
if (angular.isDefined(prefix)) {
24+
hashPrefix = prefix;
25+
}
26+
return hashPrefixFn(prefix);
27+
}
28+
29+
// Return the final hashPrefix as the value of this service
30+
this.$get = function() { return hashPrefix; };
31+
}
32+
33+
function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRootComponent, $locationHashPrefix) {
1034

1135
// When this file is processed, the line below is replaced with
1236
// the contents of `../lib/facades.es5`.

modules/angular1_router/test/ng_link_spec.js

Lines changed: 100 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,133 +2,133 @@
22

33
describe('ngLink', function () {
44

5-
it('should allow linking from the parent to the child', function () {
6-
setup();
7-
configureRouter([
8-
{ path: '/a', component: 'oneCmp' },
9-
{ path: '/b', component: 'twoCmp', name: 'Two' }
10-
]);
11-
12-
var elt = compile('<a ng-link="[\'/Two\']">link</a> | outer { <div ng-outlet></div> }');
13-
navigateTo('/a');
14-
expect(elt.find('a').attr('href')).toBe('./b');
5+
describe('html5Mode enabled', function () {
6+
runHrefTestsAndExpectPrefix(true);
157
});
168

17-
it('should allow linking from the child and the parent', function () {
18-
setup();
19-
configureRouter([
20-
{ path: '/a', component: 'oneCmp' },
21-
{ path: '/b', component: 'twoCmp', name: 'Two' }
22-
]);
9+
describe('html5Mode disabled', function () {
10+
runHrefTestsAndExpectPrefix(false, '');
11+
});
2312

24-
var elt = compile('outer { <div ng-outlet></div> }');
25-
navigateTo('/b');
26-
expect(elt.find('a').attr('href')).toBe('./b');
13+
describe('html5Mode disabled, with hash prefix', function () {
14+
runHrefTestsAndExpectPrefix(false, '!');
2715
});
2816

17+
function runHrefTestsAndExpectPrefix(html5Mode, hashPrefix) {
18+
var prefix = html5Mode ? '.' : '#' + hashPrefix;
2919

30-
it('should allow params in routerLink directive', function () {
31-
setup();
32-
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'two'});
33-
configureRouter([
34-
{ path: '/a', component: 'twoLinkCmp' },
35-
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
36-
]);
20+
it('should allow linking from the parent to the child', function () {
21+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
22+
configureRouter([
23+
{ path: '/a', component: 'oneCmp' },
24+
{ path: '/b', component: 'twoCmp', name: 'Two' }
25+
]);
3726

38-
var elt = compile('<div ng-outlet></div>');
39-
navigateTo('/a');
40-
expect(elt.find('a').attr('href')).toBe('./b/lol');
41-
});
27+
var elt = compile('<a ng-link="[\'/Two\']">link</a> | outer { <div ng-outlet></div> }');
28+
navigateTo('/a');
29+
expect(elt.find('a').attr('href')).toBe(prefix + '/b');
30+
});
4231

32+
it('should allow linking from the child and the parent', function () {
33+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
34+
configureRouter([
35+
{ path: '/a', component: 'oneCmp' },
36+
{ path: '/b', component: 'twoCmp', name: 'Two' }
37+
]);
4338

44-
it('should update the href of links with bound params', function () {
45-
setup();
46-
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: $ctrl.number}]">{{$ctrl.number}}</a></div>', function () {this.number = 43});
47-
configureRouter([
48-
{ path: '/a', component: 'twoLinkCmp' },
49-
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
50-
]);
39+
var elt = compile('outer { <div ng-outlet></div> }');
40+
navigateTo('/b');
41+
expect(elt.find('a').attr('href')).toBe(prefix + '/b');
42+
});
5143

52-
var elt = compile('<div ng-outlet></div>');
53-
navigateTo('/a');
54-
expect(elt.find('a').text()).toBe('43');
55-
expect(elt.find('a').attr('href')).toBe('./b/43');
56-
});
5744

45+
it('should allow params in routerLink directive', function () {
46+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
47+
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'two'});
48+
configureRouter([
49+
{ path: '/a', component: 'twoLinkCmp' },
50+
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
51+
]);
5852

59-
it('should navigate on left-mouse click when a link url matches a route', function () {
60-
setup();
61-
configureRouter([
62-
{ path: '/', component: 'oneCmp' },
63-
{ path: '/two', component: 'twoCmp', name: 'Two'}
64-
]);
53+
var elt = compile('<div ng-outlet></div>');
54+
navigateTo('/a');
55+
expect(elt.find('a').attr('href')).toBe(prefix + '/b/lol');
56+
});
6557

66-
var elt = compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
67-
expect(elt.text()).toBe('link | one');
68-
expect(elt.find('a').attr('href')).toBe('./two');
6958

70-
elt.find('a')[0].click();
71-
inject(function($rootScope) { $rootScope.$digest(); });
72-
expect(elt.text()).toBe('link | two');
73-
});
59+
it('should update the href of links with bound params', function () {
60+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
61+
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: $ctrl.number}]">{{$ctrl.number}}</a></div>', function () {this.number = 43});
62+
configureRouter([
63+
{ path: '/a', component: 'twoLinkCmp' },
64+
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
65+
]);
7466

67+
var elt = compile('<div ng-outlet></div>');
68+
navigateTo('/a');
69+
expect(elt.find('a').text()).toBe('43');
70+
expect(elt.find('a').attr('href')).toBe(prefix + '/b/43');
71+
});
7572

76-
it('should not navigate on non-left mouse click when a link url matches a route', function() {
77-
setup();
78-
configureRouter([
79-
{ path: '/', component: 'oneCmp' },
80-
{ path: '/two', component: 'twoCmp', name: 'Two'}
81-
]);
8273

83-
var elt = compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
84-
expect(elt.text()).toBe('link | one');
85-
elt.find('a').triggerHandler({ type: 'click', which: 3 });
86-
inject(function($rootScope) { $rootScope.$digest(); });
87-
expect(elt.text()).toBe('link | one');
88-
});
74+
it('should navigate on left-mouse click when a link url matches a route', function () {
75+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
76+
configureRouter([
77+
{ path: '/', component: 'oneCmp' },
78+
{ path: '/two', component: 'twoCmp', name: 'Two'}
79+
]);
8980

81+
var elt = compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
82+
expect(elt.text()).toBe('link | one');
83+
expect(elt.find('a').attr('href')).toBe(prefix + '/two');
9084

91-
// See https://github.com/angular/router/issues/206
92-
it('should not navigate a link without an href', function () {
93-
setup();
94-
configureRouter([
95-
{ path: '/', component: 'oneCmp' },
96-
{ path: '/two', component: 'twoCmp', name: 'Two'}
97-
]);
98-
expect(function () {
99-
var elt = compile('<a>link</a>');
100-
expect(elt.text()).toBe('link');
10185
elt.find('a')[0].click();
10286
inject(function($rootScope) { $rootScope.$digest(); });
103-
}).not.toThrow();
104-
});
87+
expect(elt.text()).toBe('link | two');
88+
});
10589

106-
it('should add an ng-link-active class on the current link', function() {
107-
setup();
108-
configureRouter([
109-
{ path: '/', component: 'oneCmp', name: 'One' }
110-
]);
11190

112-
var elt = compile('<a ng-link="[\'/One\']">one</a> | <div ng-outlet></div>');
113-
navigateTo('/');
114-
expect(elt.find('a').attr('class')).toBe('ng-link-active');
115-
});
91+
it('should not navigate on non-left mouse click when a link url matches a route', function() {
92+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
93+
configureRouter([
94+
{ path: '/', component: 'oneCmp' },
95+
{ path: '/two', component: 'twoCmp', name: 'Two'}
96+
]);
11697

98+
var elt = compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
99+
expect(elt.text()).toBe('link | one');
100+
elt.find('a').triggerHandler({ type: 'click', which: 3 });
101+
inject(function($rootScope) { $rootScope.$digest(); });
102+
expect(elt.text()).toBe('link | one');
103+
});
117104

118-
describe('html5Mode disabled', function () {
119-
it('should prepend href with a hash', function () {
120-
setup({ html5Mode: false });
121-
module(function($locationProvider) {
122-
$locationProvider.html5Mode(false);
123-
});
105+
106+
// See https://github.com/angular/router/issues/206
107+
it('should not navigate a link without an href', function () {
108+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
124109
configureRouter([
125-
{ path: '/b', component: 'twoCmp', name: 'Two' }
110+
{ path: '/', component: 'oneCmp' },
111+
{ path: '/two', component: 'twoCmp', name: 'Two'}
126112
]);
127-
var elt = compile('<a ng-link="[\'/Two\']">link</a>');
128-
expect(elt.find('a').attr('href')).toBe('#/b');
113+
expect(function () {
114+
var elt = compile('<a>link</a>');
115+
expect(elt.text()).toBe('link');
116+
elt.find('a')[0].click();
117+
inject(function($rootScope) { $rootScope.$digest(); });
118+
}).not.toThrow();
129119
});
130-
});
131120

121+
it('should add an ng-link-active class on the current link', function() {
122+
setup({html5Mode: html5Mode, hashPrefix: hashPrefix});
123+
configureRouter([
124+
{ path: '/', component: 'oneCmp', name: 'One' }
125+
]);
126+
127+
var elt = compile('<a ng-link="[\'/One\']">one</a> | <div ng-outlet></div>');
128+
navigateTo('/');
129+
expect(elt.find('a').attr('class')).toBe('ng-link-active');
130+
});
131+
}
132132

133133
function registerComponent(name, template, controller) {
134134
module(function($compileProvider) {
@@ -140,10 +140,10 @@ describe('ngLink', function () {
140140
}
141141

142142
function setup(config) {
143-
var html5Mode = !(config && config.html5Mode === false);
144143
module('ngComponentRouter')
145144
module(function($locationProvider) {
146-
$locationProvider.html5Mode(html5Mode);
145+
$locationProvider.html5Mode(config.html5Mode);
146+
$locationProvider.hashPrefix(config.hashPrefix);
147147
});
148148
registerComponent('userCmp', '<div>hello {{$ctrl.$routeParams.name}}</div>', function () {});
149149
registerComponent('oneCmp', '<div>{{$ctrl.number}}</div>', function () {this.number = 'one'});

0 commit comments

Comments
 (0)