Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 6d7e7fd

Browse files
committed
fix($location): properly rewrite urls in html5 mode with base url set
previously we were doing all kinds of checks to see if we should rewrite the url or not and we were missing many scenarios. not any more. with this change, we rewrite the url unless: - the href is not set - link has target attribute - the absolute url of the link doesn't match the absolute prefix for all urls in our app This also means that ng-ext-link attribute which we previously used to distinguish external links from app links is not necessary any more. apps can just set target=_self to prevent rewriting. BREAKING CHANGE: ng-ext-link directive was removed because it's unnecessary apps that relied on ng-ext-link should simply replace it with target=_self
1 parent df72852 commit 6d7e7fd

File tree

5 files changed

+45
-53
lines changed

5 files changed

+45
-53
lines changed

docs/content/cookbook/deeplinking.ngdoc

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Deep linking allows you to encode the state of the application in the URL so that it can be
66
bookmarked and the application can be restored from the URL to the same state.
77

8-
While <angular/> does not force you to deal with bookmarks in any particular way, it has services
8+
While angular does not force you to deal with bookmarks in any particular way, it has services
99
which make the common case described here very easy to implement.
1010

1111
# Assumptions
@@ -33,8 +33,8 @@ In this example we have a simple app which consist of two screens:
3333

3434
The two partials are defined in the following URLs:
3535

36-
* <a href="./examples/settings.html" ng-ext-link>./examples/settings.html</a>
37-
* <a href="./examples/welcome.html" ng-ext-link>./examples/welcome.html</a>
36+
* <a href="examples/settings.html" target="_self">./examples/settings.html</a>
37+
* <a href="examples/welcome.html" target="_self">./examples/welcome.html</a>
3838

3939
<doc:example module="deepLinking">
4040
<doc:source jsfiddle="false">

docs/content/guide/dev_guide.services.$location.ngdoc

+19-16
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ history API or not; the `$location` service makes this transparent to you.
305305
### Html link rewriting
306306

307307
When you use the history API mode, you will need different links in different browser, but all you
308-
have to do is specify regular URL links, such as: `&lt;a href="/some?foo=bar"&gt;link&lt;/a&gt;`
308+
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
309309

310310
When a user clicks on this link,
311311

@@ -316,12 +316,13 @@ When a user clicks on this link,
316316
In cases like the following, links are not rewritten; instead, the browser will perform a full page
317317
reload to the original link.
318318

319-
- Links with an `ngExtLink` directive<br />
320-
Example: `<a href="/ext/link?a=b" ng-ext-link>link</a>`
321-
- Links that contain `target="_blank"`<br />
322-
Example: `<a href="/ext/link?a=b" target="_blank">link</a>`
323-
- Absolute links that go to a different domain<br />
319+
- Links that contain `target` element<br>
320+
Example: `<a href="/ext/link?a=b" target="_self">link</a>`
321+
- Absolute links that go to a different domain<br>
324322
Example: `<a href="http://angularjs.org/">link</a>`
323+
- Links starting with '/' that lead to a different base path when base is defined<br>
324+
Example: `<a href="/not-my-base/link">link</a>`
325+
325326

326327
### Server side
327328

@@ -341,11 +342,13 @@ Applications Crawlable}.
341342

342343
### Relative links
343344

344-
Be sure to check all relative links, images, scripts etc. You must use an absolute path because the
345-
path is going to be rewritten. You can use `<base href="" />` tag as well.
345+
Be sure to check all relative links, images, scripts etc. You must either specify the url base in
346+
the head of your main html file (`<base href="/my-base">`) or you must use absolute urls
347+
(starting with `/`) everywhere because relative urls will be resolved to absolute urls using the
348+
initial absolute url of the document, which is often different from the root of the application.
346349

347350
Running Angular apps with the History API enabled from document root is strongly encouraged as it
348-
takes care of all relative link issues. **Otherwise you have to specify &lt;base href="" /&gt; !**
351+
takes care of all relative link issues.
349352

350353
### Sending links among different browsers
351354

@@ -379,9 +382,9 @@ In this examples we use `<base href="/base/index.html" />`
379382
$location.path() = {{$location.path()}}<br>
380383
$location.search() = {{$location.search()}}<br>
381384
$location.hash() = {{$location.hash()}}<br>
382-
<a href="/base/first?a=b">/base/first?a=b</a> |
383-
<a href="sec/ond?flag#hash">sec/ond?flag#hash</a> |
384-
<a href="/base/another?search" ng-ext-link>external</a>
385+
<a href="http://www.host.com/base/first?a=b">/base/first?a=b</a> |
386+
<a href="http://www.host.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
387+
<a href="/other-base/another?search">external</a>
385388
</div>
386389

387390
<div id="hashbang-mode" ng-controller="HashbangCntl">
@@ -393,9 +396,9 @@ In this examples we use `<base href="/base/index.html" />`
393396
$location.path() = {{$location.path()}}<br>
394397
$location.search() = {{$location.search()}}<br>
395398
$location.hash() = {{$location.hash()}}<br>
396-
<a href="/base/first?a=b">/base/first?a=b</a> |
397-
<a href="sec/ond?flag#hash">sec/ond?flag#hash</a> |
398-
<a href="/base/another?search" ng-ext-link>external</a>
399+
<a href="http://www.host.com/base/first?a=b">/base/first?a=b</a> |
400+
<a href="http://www.host.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
401+
<a href="/other-base/another?search">external</a>
399402
</div>
400403
</div>
401404

@@ -445,7 +448,7 @@ In this examples we use `<base href="/base/index.html" />`
445448
$compileProvider.directive('ngAddressBar', function() {
446449
return function(scope, elm, attrs) {
447450
var browser = browsers[attrs.browser],
448-
input = angular.element('<input type="text" />').val(browser.url()),
451+
input = angular.element('<input type="text">').val(browser.url()),
449452
delay;
450453

451454
input.bind('keypress keyup keydown', function() {

src/ng/directive/booleanAttrs.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @restrict A
77
*
88
* @description
9-
* Using <angular/> markup like {{hash}} in an href attribute makes
9+
* Using Angular markup like {{hash}} in an href attribute makes
1010
* the page open to a wrong URL, if the user clicks that link before
1111
* angular has a chance to replace the {{hash}} with actual URL, the
1212
* link will be broken and will most likely return a 404 error.
@@ -32,10 +32,10 @@
3232
<input ng-model="value" /><br />
3333
<a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br />
3434
<a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br />
35-
<a id="link-3" ng-href="/{{'123'}}" ng-ext-link>link 3</a> (link, reload!)<br />
35+
<a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br />
3636
<a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br />
3737
<a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
38-
<a id="link-6" ng-href="/{{value}}" ng-ext-link>link</a> (link, change hash)
38+
<a id="link-6" ng-href="{{value}}">link</a> (link, change location)
3939
</doc:source>
4040
<doc:scenario>
4141
it('should execute ng-click but not reload when href without value', function() {
@@ -60,21 +60,21 @@
6060
it('should execute ng-click but not reload when href empty string and name specified', function() {
6161
element('#link-4').click();
6262
expect(input('value').val()).toEqual('4');
63-
expect(element('#link-4').attr('href')).toBe("");
63+
expect(element('#link-4').attr('href')).toBe('');
6464
});
6565
6666
it('should execute ng-click but not reload when no href but name specified', function() {
6767
element('#link-5').click();
6868
expect(input('value').val()).toEqual('5');
69-
expect(element('#link-5').attr('href')).toBe("");
69+
expect(element('#link-5').attr('href')).toBe('');
7070
});
7171
7272
it('should only change url when only ng-href', function() {
7373
input('value').enter('6');
74-
expect(element('#link-6').attr('href')).toBe("/6");
74+
expect(element('#link-6').attr('href')).toBe('6');
7575
7676
element('#link-6').click();
77-
expect(browser().window().path()).toEqual('/6');
77+
expect(browser().location().url()).toEqual('/6');
7878
});
7979
</doc:scenario>
8080
</doc:example>

src/ng/location.js

+8-14
Original file line numberDiff line numberDiff line change
@@ -500,22 +500,16 @@ function $LocationProvider(){
500500
elm = elm.parent();
501501
}
502502

503-
var href = elm.attr('href');
504-
if (!href || isDefined(elm.attr('ng-ext-link')) || elm.attr('target')) return;
503+
var absHref = elm.prop('href');
505504

506-
// link to different base path
507-
if (href[0] === '/' && href.indexOf(pathPrefix) !== 0) return;
508-
509-
// remove same domain from full url links (IE7 always returns full hrefs)
510-
href = href.replace(absUrlPrefix, '');
511-
512-
// link to different domain (or base path)
513-
if (href.substr(0, 4) == 'http') return;
514-
515-
// remove pathPrefix from absolute links
516-
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
505+
if (!absHref ||
506+
elm.attr('target') ||
507+
absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
508+
return;
509+
}
517510

518-
currentUrl.url(href);
511+
// update location with href without the prefix
512+
currentUrl.url(absHref.substr(absUrlPrefix.length));
519513
$rootScope.$apply();
520514
event.preventDefault();
521515
// hack to work around FF6 bug 684208 when scenario runner clicks on links

test/ng/locationSpec.js

+8-13
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,14 @@ describe('$location', function() {
672672
module(function($provide, $locationProvider) {
673673
var jqRoot = jqLite('<div></div>');
674674
attrs = attrs ? ' ' + attrs + ' ' : '';
675+
676+
// fake the base behavior
677+
if (linkHref[0] == '/') {
678+
linkHref = 'http://host.com' + linkHref;
679+
} else if(!linkHref.match(/:\/\//)) {
680+
linkHref = 'http://host.com/base/' + linkHref;
681+
}
682+
675683
link = jqLite('<a href="' + linkHref + '"' + attrs + '>' + content + '</a>')[0];
676684
root = jqRoot.append(link)[0];
677685

@@ -784,19 +792,6 @@ describe('$location', function() {
784792
});
785793

786794

787-
it('should not rewrite ngExtLink', function() {
788-
configureService('#new', true, true, 'ng-ext-link');
789-
inject(
790-
initBrowser(),
791-
initLocation(),
792-
function($browser) {
793-
browserTrigger(link, 'click');
794-
expectNoRewrite($browser);
795-
}
796-
);
797-
});
798-
799-
800795
it('should not rewrite full url links do different domain', function() {
801796
configureService('http://www.dot.abc/a?b=c', true);
802797
inject(

0 commit comments

Comments
 (0)