Skip to content

Commit

Permalink
feat(router): add support for APP_BASE_HREF to HashLocationStrategy
Browse files Browse the repository at this point in the history
Closes #4935
Closes #5368

Closes #5451
  • Loading branch information
btford committed Nov 25, 2015
1 parent b6ec238 commit 1bec4f6
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 47 deletions.
3 changes: 3 additions & 0 deletions modules/angular2/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {RouterOutlet} from './src/router/router_outlet';
export {RouterLink} from './src/router/router_link';
export {RouteParams, RouteData} from './src/router/instruction';
export {RouteRegistry} from './src/router/route_registry';
export {PlatformLocation} from './src/router/platform_location';
export {LocationStrategy, APP_BASE_HREF} from './src/router/location_strategy';
export {HashLocationStrategy} from './src/router/hash_location_strategy';
export {PathLocationStrategy} from './src/router/path_location_strategy';
Expand All @@ -20,6 +21,7 @@ export {CanActivate} from './src/router/lifecycle_annotations';
export {Instruction, ComponentInstruction} from './src/router/instruction';
export {OpaqueToken} from 'angular2/core';

import {PlatformLocation} from './src/router/platform_location';
import {LocationStrategy} from './src/router/location_strategy';
import {PathLocationStrategy} from './src/router/path_location_strategy';
import {Router, RootRouter} from './src/router/router';
Expand Down Expand Up @@ -111,6 +113,7 @@ export const ROUTER_DIRECTIVES: any[] = CONST_EXPR([RouterOutlet, RouterLink]);
export const ROUTER_PROVIDERS: any[] = CONST_EXPR([
RouteRegistry,
CONST_EXPR(new Provider(LocationStrategy, {useClass: PathLocationStrategy})),
PlatformLocation,
Location,
CONST_EXPR(new Provider(
Router,
Expand Down
51 changes: 27 additions & 24 deletions modules/angular2/src/router/hash_location_strategy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable} from 'angular2/core';
import {LocationStrategy, normalizeQueryParams} from './location_strategy';
import {EventListener, History, Location} from 'angular2/src/facade/browser';
import {Injectable, Inject, Optional} from 'angular2/core';
import {
LocationStrategy,
joinWithSlash,
APP_BASE_HREF,
normalizeQueryParams
} from './location_strategy';
import {EventListener} from 'angular2/src/facade/browser';
import {isPresent} from 'angular2/src/facade/lang';
import {PlatformLocation} from './platform_location';

/**
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
Expand Down Expand Up @@ -43,48 +49,45 @@ import {EventListener, History, Location} from 'angular2/src/facade/browser';
*/
@Injectable()
export class HashLocationStrategy extends LocationStrategy {
private _location: Location;
private _history: History;

constructor() {
private _baseHref: string = '';
constructor(private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
super();
this._location = DOM.getLocation();
this._history = DOM.getHistory();
if (isPresent(_baseHref)) {
this._baseHref = _baseHref;
}
}

onPopState(fn: EventListener): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
}
onPopState(fn: EventListener): void { this._platformLocation.onPopState(fn); }

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

path(): string {
// the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty
var path = this._location.hash;
var path = this._platformLocation.hash;

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

prepareExternalUrl(internal: string): string {
return internal.length > 0 ? ('#' + internal) : internal;
var url = joinWithSlash(this._baseHref, internal);
return url.length > 0 ? ('#' + url) : url;
}

pushState(state: any, title: string, path: string, queryParams: string) {
var url = path + normalizeQueryParams(queryParams);
var url = this.prepareExternalUrl(path + normalizeQueryParams(queryParams));
if (url.length == 0) {
url = this._location.pathname;
} else {
url = this.prepareExternalUrl(url);
url = this._platformLocation.pathname;
}
this._history.pushState(state, title, url);
this._platformLocation.pushState(state, title, url);
}

forward(): void { this._history.forward(); }
forward(): void { this._platformLocation.forward(); }

back(): void { this._history.back(); }
back(): void { this._platformLocation.back(); }
}
23 changes: 23 additions & 0 deletions modules/angular2/src/router/location_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,26 @@ export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHre
export function normalizeQueryParams(params: string): string {
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
}

export function joinWithSlash(start: string, end: string): string {
if (start.length == 0) {
return end;
}
if (end.length == 0) {
return start;
}
var slashes = 0;
if (start.endsWith('/')) {
slashes++;
}
if (end.startsWith('/')) {
slashes++;
}
if (slashes == 2) {
return start + end.substring(1);
}
if (slashes == 1) {
return start + end;
}
return start + '/' + end;
}
41 changes: 20 additions & 21 deletions modules/angular2/src/router/path_location_strategy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable, Inject} from 'angular2/core';
import {Injectable, Inject, Optional} from 'angular2/core';
import {EventListener, History, Location} from 'angular2/src/facade/browser';
import {isBlank} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {LocationStrategy, APP_BASE_HREF, normalizeQueryParams} from './location_strategy';
import {
LocationStrategy,
APP_BASE_HREF,
normalizeQueryParams,
joinWithSlash
} from './location_strategy';
import {PlatformLocation} from './platform_location';

/**
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
Expand Down Expand Up @@ -52,49 +57,43 @@ import {LocationStrategy, APP_BASE_HREF, normalizeQueryParams} from './location_
*/
@Injectable()
export class PathLocationStrategy extends LocationStrategy {
private _location: Location;
private _history: History;
private _baseHref: string;

constructor(@Inject(APP_BASE_HREF) href?: string) {
constructor(private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) href?: string) {
super();

if (isBlank(href)) {
href = DOM.getBaseHref();
href = this._platformLocation.getBaseHrefFromDOM();
}

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

this._location = DOM.getLocation();
this._history = DOM.getHistory();
this._baseHref = href;
}

onPopState(fn: EventListener): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}

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

prepareExternalUrl(internal: string): string {
if (internal.startsWith('/') && this._baseHref.endsWith('/')) {
return this._baseHref + internal.substring(1);
}
return this._baseHref + internal;
}
prepareExternalUrl(internal: string): string { return joinWithSlash(this._baseHref, internal); }

path(): string { return this._location.pathname + normalizeQueryParams(this._location.search); }
path(): string {
return this._platformLocation.pathname + normalizeQueryParams(this._platformLocation.search);
}

pushState(state: any, title: string, url: string, queryParams: string) {
var externalUrl = this.prepareExternalUrl(url + normalizeQueryParams(queryParams));
this._history.pushState(state, title, externalUrl);
this._platformLocation.pushState(state, title, externalUrl);
}

forward(): void { this._history.forward(); }
forward(): void { this._platformLocation.forward(); }

back(): void { this._history.back(); }
back(): void { this._platformLocation.back(); }
}
46 changes: 46 additions & 0 deletions modules/angular2/src/router/platform_location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable} from 'angular2/core';
import {EventListener, History, Location} from 'angular2/src/facade/browser';

/**
* `PlatformLocation` encapsulates all of the direct calls to platform APIs.
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
*/
@Injectable()
export class PlatformLocation {
private _location: Location;
private _history: History;

constructor() { this._init(); }

// This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it
/** @internal */
_init() {
this._location = DOM.getLocation();
this._history = DOM.getHistory();
}

getBaseHrefFromDOM(): string { return DOM.getBaseHref(); }

onPopState(fn: EventListener): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
}

onHashChange(fn: EventListener): void {
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
}

get pathname(): string { return this._location.pathname; }
get search(): string { return this._location.search; }
get hash(): string { return this._location.hash; }
set pathname(newPath: string) { this._location.pathname = newPath; }

pushState(state: any, title: string, url: string): void {
this._history.pushState(state, title, url);
}

forward(): void { this._history.forward(); }

back(): void { this._history.back(); }
}
Loading

0 comments on commit 1bec4f6

Please sign in to comment.