Skip to content

Commit 560cc14

Browse files
committed
feat(router): change location when navigating
1 parent de56dd5 commit 560cc14

File tree

11 files changed

+589
-11
lines changed

11 files changed

+589
-11
lines changed

modules/angular2/alt_router.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export {
1414
} from './src/alt_router/router_url_serializer';
1515
export {OnActivate} from './src/alt_router/interfaces';
1616

17+
export {Location} from './src/alt_router/location/location';
18+
export {LocationStrategy} from './src/alt_router/location/location_strategy';
19+
export {PathLocationStrategy} from './src/alt_router/location/path_location_strategy';
20+
export {HashLocationStrategy} from './src/alt_router/location/hash_location_strategy';
21+
export {PlatformLocation} from './src/alt_router/location/platform_location';
22+
export {BrowserPlatformLocation} from './src/alt_router/location/browser_platform_location';
23+
1724
import {RouterOutlet} from './src/alt_router/directives/router_outlet';
1825
import {RouterLink} from './src/alt_router/directives/router_link';
1926
import {CONST_EXPR} from './src/facade/lang';
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {Injectable} from 'angular2/src/core/di/decorators';
2+
import {UrlChangeListener, PlatformLocation} from './platform_location';
3+
import {History, Location} from 'angular2/src/facade/browser';
4+
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
5+
6+
/**
7+
* `PlatformLocation` encapsulates all of the direct calls to platform APIs.
8+
* This class should not be used directly by an application developer. Instead, use
9+
* {@link Location}.
10+
*/
11+
@Injectable()
12+
export class BrowserPlatformLocation extends PlatformLocation {
13+
private _location: Location;
14+
private _history: History;
15+
16+
constructor() {
17+
super();
18+
this._init();
19+
}
20+
21+
// This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it
22+
/** @internal */
23+
_init() {
24+
this._location = DOM.getLocation();
25+
this._history = DOM.getHistory();
26+
}
27+
28+
/** @internal */
29+
get location(): Location { return this._location; }
30+
31+
getBaseHrefFromDOM(): string { return DOM.getBaseHref(); }
32+
33+
onPopState(fn: UrlChangeListener): void {
34+
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
35+
}
36+
37+
onHashChange(fn: UrlChangeListener): void {
38+
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
39+
}
40+
41+
get pathname(): string { return this._location.pathname; }
42+
get search(): string { return this._location.search; }
43+
get hash(): string { return this._location.hash; }
44+
set pathname(newPath: string) { this._location.pathname = newPath; }
45+
46+
pushState(state: any, title: string, url: string): void {
47+
this._history.pushState(state, title, url);
48+
}
49+
50+
replaceState(state: any, title: string, url: string): void {
51+
this._history.replaceState(state, title, url);
52+
}
53+
54+
forward(): void { this._history.forward(); }
55+
56+
back(): void { this._history.back(); }
57+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {Injectable, Inject, Optional} from 'angular2/core';
2+
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
3+
import {Location} from './location';
4+
import {UrlChangeListener, PlatformLocation} from './platform_location';
5+
import {isPresent} from 'angular2/src/facade/lang';
6+
7+
/**
8+
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
9+
* {@link Location} service to represent its state in the
10+
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
11+
* of the browser's URL.
12+
*
13+
* For instance, if you call `location.go('/foo')`, the browser's URL will become
14+
* `example.com#/foo`.
15+
*
16+
* ### Example
17+
*
18+
* ```
19+
* import {Component, provide} from 'angular2/core';
20+
* import {
21+
* Location,
22+
* LocationStrategy,
23+
* HashLocationStrategy
24+
* } from 'angular2/platform/common';
25+
* import {
26+
* ROUTER_DIRECTIVES,
27+
* ROUTER_PROVIDERS,
28+
* RouteConfig
29+
* } from 'angular2/router';
30+
*
31+
* @Component({directives: [ROUTER_DIRECTIVES]})
32+
* @RouteConfig([
33+
* {...},
34+
* ])
35+
* class AppCmp {
36+
* constructor(location: Location) {
37+
* location.go('/foo');
38+
* }
39+
* }
40+
*
41+
* bootstrap(AppCmp, [
42+
* ROUTER_PROVIDERS,
43+
* provide(LocationStrategy, {useClass: HashLocationStrategy})
44+
* ]);
45+
* ```
46+
*/
47+
@Injectable()
48+
export class HashLocationStrategy extends LocationStrategy {
49+
private _baseHref: string = '';
50+
constructor(private _platformLocation: PlatformLocation,
51+
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
52+
super();
53+
if (isPresent(_baseHref)) {
54+
this._baseHref = _baseHref;
55+
}
56+
}
57+
58+
onPopState(fn: UrlChangeListener): void {
59+
this._platformLocation.onPopState(fn);
60+
this._platformLocation.onHashChange(fn);
61+
}
62+
63+
getBaseHref(): string { return this._baseHref; }
64+
65+
path(): string {
66+
// the hash value is always prefixed with a `#`
67+
// and if it is empty then it will stay empty
68+
var path = this._platformLocation.hash;
69+
if (!isPresent(path)) path = '#';
70+
71+
// Dart will complain if a call to substring is
72+
// executed with a position value that extends the
73+
// length of string.
74+
return (path.length > 0 ? path.substring(1) : path);
75+
}
76+
77+
prepareExternalUrl(internal: string): string {
78+
var url = Location.joinWithSlash(this._baseHref, internal);
79+
return url.length > 0 ? ('#' + url) : url;
80+
}
81+
82+
pushState(state: any, title: string, path: string, queryParams: string) {
83+
var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
84+
if (url.length == 0) {
85+
url = this._platformLocation.pathname;
86+
}
87+
this._platformLocation.pushState(state, title, url);
88+
}
89+
90+
replaceState(state: any, title: string, path: string, queryParams: string) {
91+
var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
92+
if (url.length == 0) {
93+
url = this._platformLocation.pathname;
94+
}
95+
this._platformLocation.replaceState(state, title, url);
96+
}
97+
98+
forward(): void { this._platformLocation.forward(); }
99+
100+
back(): void { this._platformLocation.back(); }
101+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
2+
import {Injectable, Inject} from 'angular2/core';
3+
import {LocationStrategy} from './location_strategy';
4+
5+
/**
6+
* `Location` is a service that applications can use to interact with a browser's URL.
7+
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
8+
* to the URL's path or the URL's hash segment.
9+
*
10+
* Note: it's better to use {@link Router#navigate} service to trigger route changes. Use
11+
* `Location` only if you need to interact with or create normalized URLs outside of
12+
* routing.
13+
*
14+
* `Location` is responsible for normalizing the URL against the application's base href.
15+
* A normalized URL is absolute from the URL host, includes the application's base href, and has no
16+
* trailing slash:
17+
* - `/my/app/user/123` is normalized
18+
* - `my/app/user/123` **is not** normalized
19+
* - `/my/app/user/123/` **is not** normalized
20+
*
21+
* ### Example
22+
*
23+
* ```
24+
* import {Component} from 'angular2/core';
25+
* import {Location} from 'angular2/platform/common';
26+
* import {
27+
* ROUTER_DIRECTIVES,
28+
* ROUTER_PROVIDERS,
29+
* RouteConfig
30+
* } from 'angular2/router';
31+
*
32+
* @Component({directives: [ROUTER_DIRECTIVES]})
33+
* @RouteConfig([
34+
* {...},
35+
* ])
36+
* class AppCmp {
37+
* constructor(location: Location) {
38+
* location.go('/foo');
39+
* }
40+
* }
41+
*
42+
* bootstrap(AppCmp, [ROUTER_PROVIDERS]);
43+
* ```
44+
*/
45+
@Injectable()
46+
export class Location {
47+
/** @internal */
48+
_subject: EventEmitter<any> = new EventEmitter();
49+
/** @internal */
50+
_baseHref: string;
51+
52+
constructor(public platformStrategy: LocationStrategy) {
53+
var browserBaseHref = this.platformStrategy.getBaseHref();
54+
this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref));
55+
this.platformStrategy.onPopState((ev) => {
56+
ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true, 'type': ev.type});
57+
});
58+
}
59+
60+
/**
61+
* Returns the normalized URL path.
62+
*/
63+
path(): string { return this.normalize(this.platformStrategy.path()); }
64+
65+
/**
66+
* Given a string representing a URL, returns the normalized URL path without leading or
67+
* trailing slashes
68+
*/
69+
normalize(url: string): string {
70+
return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url)));
71+
}
72+
73+
/**
74+
* Given a string representing a URL, returns the platform-specific external URL path.
75+
* If the given URL doesn't begin with a leading slash (`'/'`), this method adds one
76+
* before normalizing. This method will also add a hash if `HashLocationStrategy` is
77+
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
78+
*/
79+
prepareExternalUrl(url: string): string {
80+
if (url.length > 0 && !url.startsWith('/')) {
81+
url = '/' + url;
82+
}
83+
return this.platformStrategy.prepareExternalUrl(url);
84+
}
85+
86+
// TODO: rename this method to pushState
87+
/**
88+
* Changes the browsers URL to the normalized version of the given URL, and pushes a
89+
* new item onto the platform's history.
90+
*/
91+
go(path: string, query: string = ''): void {
92+
this.platformStrategy.pushState(null, '', path, query);
93+
}
94+
95+
/**
96+
* Changes the browsers URL to the normalized version of the given URL, and replaces
97+
* the top item on the platform's history stack.
98+
*/
99+
replaceState(path: string, query: string = ''): void {
100+
this.platformStrategy.replaceState(null, '', path, query);
101+
}
102+
103+
/**
104+
* Navigates forward in the platform's history.
105+
*/
106+
forward(): void { this.platformStrategy.forward(); }
107+
108+
/**
109+
* Navigates back in the platform's history.
110+
*/
111+
back(): void { this.platformStrategy.back(); }
112+
113+
/**
114+
* Subscribe to the platform's `popState` events.
115+
*/
116+
subscribe(onNext: (value: any) => void, onThrow: (exception: any) => void = null,
117+
onReturn: () => void = null): Object {
118+
return ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
119+
}
120+
121+
/**
122+
* Given a string of url parameters, prepend with '?' if needed, otherwise return parameters as
123+
* is.
124+
*/
125+
public static normalizeQueryParams(params: string): string {
126+
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
127+
}
128+
129+
/**
130+
* Given 2 parts of a url, join them with a slash if needed.
131+
*/
132+
public static joinWithSlash(start: string, end: string): string {
133+
if (start.length == 0) {
134+
return end;
135+
}
136+
if (end.length == 0) {
137+
return start;
138+
}
139+
var slashes = 0;
140+
if (start.endsWith('/')) {
141+
slashes++;
142+
}
143+
if (end.startsWith('/')) {
144+
slashes++;
145+
}
146+
if (slashes == 2) {
147+
return start + end.substring(1);
148+
}
149+
if (slashes == 1) {
150+
return start + end;
151+
}
152+
return start + '/' + end;
153+
}
154+
155+
/**
156+
* If url has a trailing slash, remove it, otherwise return url as is.
157+
*/
158+
public static stripTrailingSlash(url: string): string {
159+
if (/\/$/g.test(url)) {
160+
url = url.substring(0, url.length - 1);
161+
}
162+
return url;
163+
}
164+
}
165+
166+
function _stripBaseHref(baseHref: string, url: string): string {
167+
if (baseHref.length > 0 && url.startsWith(baseHref)) {
168+
return url.substring(baseHref.length);
169+
}
170+
return url;
171+
}
172+
173+
function _stripIndexHtml(url: string): string {
174+
if (/\/index.html$/g.test(url)) {
175+
// '/index.html'.length == 11
176+
return url.substring(0, url.length - 11);
177+
}
178+
return url;
179+
}

0 commit comments

Comments
 (0)