Skip to content

Commit 1bec4f6

Browse files
committed
feat(router): add support for APP_BASE_HREF to HashLocationStrategy
Closes #4935 Closes #5368 Closes #5451
1 parent b6ec238 commit 1bec4f6

File tree

9 files changed

+476
-47
lines changed

9 files changed

+476
-47
lines changed

modules/angular2/router.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export {RouterOutlet} from './src/router/router_outlet';
99
export {RouterLink} from './src/router/router_link';
1010
export {RouteParams, RouteData} from './src/router/instruction';
1111
export {RouteRegistry} from './src/router/route_registry';
12+
export {PlatformLocation} from './src/router/platform_location';
1213
export {LocationStrategy, APP_BASE_HREF} from './src/router/location_strategy';
1314
export {HashLocationStrategy} from './src/router/hash_location_strategy';
1415
export {PathLocationStrategy} from './src/router/path_location_strategy';
@@ -20,6 +21,7 @@ export {CanActivate} from './src/router/lifecycle_annotations';
2021
export {Instruction, ComponentInstruction} from './src/router/instruction';
2122
export {OpaqueToken} from 'angular2/core';
2223

24+
import {PlatformLocation} from './src/router/platform_location';
2325
import {LocationStrategy} from './src/router/location_strategy';
2426
import {PathLocationStrategy} from './src/router/path_location_strategy';
2527
import {Router, RootRouter} from './src/router/router';
@@ -111,6 +113,7 @@ export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]);
111113
export const ROUTER_PROVIDERS: any[] = CONST_EXPR([
112114
RouteRegistry,
113115
CONST_EXPR(new Provider(LocationStrategy, {useClass: PathLocationStrategy})),
116+
PlatformLocation,
114117
Location,
115118
CONST_EXPR(new Provider(
116119
Router,

modules/angular2/src/router/hash_location_strategy.ts

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
2-
import {Injectable} from 'angular2/core';
3-
import {LocationStrategy, normalizeQueryParams} from './location_strategy';
4-
import {EventListener, History, Location} from 'angular2/src/facade/browser';
1+
import {Injectable, Inject, Optional} from 'angular2/core';
2+
import {
3+
LocationStrategy,
4+
joinWithSlash,
5+
APP_BASE_HREF,
6+
normalizeQueryParams
7+
} from './location_strategy';
8+
import {EventListener} from 'angular2/src/facade/browser';
9+
import {isPresent} from 'angular2/src/facade/lang';
10+
import {PlatformLocation} from './platform_location';
511

612
/**
713
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
@@ -43,48 +49,45 @@ import {EventListener, History, Location} from 'angular2/src/facade/browser';
4349
*/
4450
@Injectable()
4551
export class HashLocationStrategy extends LocationStrategy {
46-
private _location: Location;
47-
private _history: History;
48-
49-
constructor() {
52+
private _baseHref: string = '';
53+
constructor(private _platformLocation: PlatformLocation,
54+
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
5055
super();
51-
this._location = DOM.getLocation();
52-
this._history = DOM.getHistory();
56+
if (isPresent(_baseHref)) {
57+
this._baseHref = _baseHref;
58+
}
5359
}
5460

55-
onPopState(fn: EventListener): void {
56-
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
57-
}
61+
onPopState(fn: EventListener): void { this._platformLocation.onPopState(fn); }
5862

59-
getBaseHref(): string { return ''; }
63+
getBaseHref(): string { return this._baseHref; }
6064

6165
path(): string {
6266
// the hash value is always prefixed with a `#`
6367
// and if it is empty then it will stay empty
64-
var path = this._location.hash;
68+
var path = this._platformLocation.hash;
6569

6670
// Dart will complain if a call to substring is
6771
// executed with a position value that extends the
6872
// length of string.
6973
return (path.length > 0 ? path.substring(1) : path) +
70-
normalizeQueryParams(this._location.search);
74+
normalizeQueryParams(this._platformLocation.search);
7175
}
7276

7377
prepareExternalUrl(internal: string): string {
74-
return internal.length > 0 ? ('#' + internal) : internal;
78+
var url = joinWithSlash(this._baseHref, internal);
79+
return url.length > 0 ? ('#' + url) : url;
7580
}
7681

7782
pushState(state: any, title: string, path: string, queryParams: string) {
78-
var url = path + normalizeQueryParams(queryParams);
83+
var url = this.prepareExternalUrl(path + normalizeQueryParams(queryParams));
7984
if (url.length == 0) {
80-
url = this._location.pathname;
81-
} else {
82-
url = this.prepareExternalUrl(url);
85+
url = this._platformLocation.pathname;
8386
}
84-
this._history.pushState(state, title, url);
87+
this._platformLocation.pushState(state, title, url);
8588
}
8689

87-
forward(): void { this._history.forward(); }
90+
forward(): void { this._platformLocation.forward(); }
8891

89-
back(): void { this._history.back(); }
92+
back(): void { this._platformLocation.back(); }
9093
}

modules/angular2/src/router/location_strategy.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,26 @@ export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHre
6262
export function normalizeQueryParams(params: string): string {
6363
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
6464
}
65+
66+
export function joinWithSlash(start: string, end: string): string {
67+
if (start.length == 0) {
68+
return end;
69+
}
70+
if (end.length == 0) {
71+
return start;
72+
}
73+
var slashes = 0;
74+
if (start.endsWith('/')) {
75+
slashes++;
76+
}
77+
if (end.startsWith('/')) {
78+
slashes++;
79+
}
80+
if (slashes == 2) {
81+
return start + end.substring(1);
82+
}
83+
if (slashes == 1) {
84+
return start + end;
85+
}
86+
return start + '/' + end;
87+
}

modules/angular2/src/router/path_location_strategy.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
2-
import {Injectable, Inject} from 'angular2/core';
1+
import {Injectable, Inject, Optional} from 'angular2/core';
32
import {EventListener, History, Location} from 'angular2/src/facade/browser';
43
import {isBlank} from 'angular2/src/facade/lang';
54
import {BaseException} from 'angular2/src/facade/exceptions';
6-
import {LocationStrategy, APP_BASE_HREF, normalizeQueryParams} from './location_strategy';
5+
import {
6+
LocationStrategy,
7+
APP_BASE_HREF,
8+
normalizeQueryParams,
9+
joinWithSlash
10+
} from './location_strategy';
11+
import {PlatformLocation} from './platform_location';
712

813
/**
914
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
@@ -52,49 +57,43 @@ import {LocationStrategy, APP_BASE_HREF, normalizeQueryParams} from './location_
5257
*/
5358
@Injectable()
5459
export class PathLocationStrategy extends LocationStrategy {
55-
private _location: Location;
56-
private _history: History;
5760
private _baseHref: string;
5861

59-
constructor(@Inject(APP_BASE_HREF) href?: string) {
62+
constructor(private _platformLocation: PlatformLocation,
63+
@Optional() @Inject(APP_BASE_HREF) href?: string) {
6064
super();
6165

6266
if (isBlank(href)) {
63-
href = DOM.getBaseHref();
67+
href = this._platformLocation.getBaseHrefFromDOM();
6468
}
6569

6670
if (isBlank(href)) {
6771
throw new BaseException(
6872
`No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`);
6973
}
7074

71-
this._location = DOM.getLocation();
72-
this._history = DOM.getHistory();
7375
this._baseHref = href;
7476
}
7577

7678
onPopState(fn: EventListener): void {
77-
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
78-
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
79+
this._platformLocation.onPopState(fn);
80+
this._platformLocation.onHashChange(fn);
7981
}
8082

8183
getBaseHref(): string { return this._baseHref; }
8284

83-
prepareExternalUrl(internal: string): string {
84-
if (internal.startsWith('/') && this._baseHref.endsWith('/')) {
85-
return this._baseHref + internal.substring(1);
86-
}
87-
return this._baseHref + internal;
88-
}
85+
prepareExternalUrl(internal: string): string { return joinWithSlash(this._baseHref, internal); }
8986

90-
path(): string { return this._location.pathname + normalizeQueryParams(this._location.search); }
87+
path(): string {
88+
return this._platformLocation.pathname + normalizeQueryParams(this._platformLocation.search);
89+
}
9190

9291
pushState(state: any, title: string, url: string, queryParams: string) {
9392
var externalUrl = this.prepareExternalUrl(url + normalizeQueryParams(queryParams));
94-
this._history.pushState(state, title, externalUrl);
93+
this._platformLocation.pushState(state, title, externalUrl);
9594
}
9695

97-
forward(): void { this._history.forward(); }
96+
forward(): void { this._platformLocation.forward(); }
9897

99-
back(): void { this._history.back(); }
98+
back(): void { this._platformLocation.back(); }
10099
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
2+
import {Injectable} from 'angular2/core';
3+
import {EventListener, History, Location} from 'angular2/src/facade/browser';
4+
5+
/**
6+
* `PlatformLocation` encapsulates all of the direct calls to platform APIs.
7+
* This class should not be used directly by an application developer. Instead, use
8+
* {@link Location}.
9+
*/
10+
@Injectable()
11+
export class PlatformLocation {
12+
private _location: Location;
13+
private _history: History;
14+
15+
constructor() { this._init(); }
16+
17+
// This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it
18+
/** @internal */
19+
_init() {
20+
this._location = DOM.getLocation();
21+
this._history = DOM.getHistory();
22+
}
23+
24+
getBaseHrefFromDOM(): string { return DOM.getBaseHref(); }
25+
26+
onPopState(fn: EventListener): void {
27+
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
28+
}
29+
30+
onHashChange(fn: EventListener): void {
31+
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
32+
}
33+
34+
get pathname(): string { return this._location.pathname; }
35+
get search(): string { return this._location.search; }
36+
get hash(): string { return this._location.hash; }
37+
set pathname(newPath: string) { this._location.pathname = newPath; }
38+
39+
pushState(state: any, title: string, url: string): void {
40+
this._history.pushState(state, title, url);
41+
}
42+
43+
forward(): void { this._history.forward(); }
44+
45+
back(): void { this._history.back(); }
46+
}

0 commit comments

Comments
 (0)