From ac388128096e54c94f0bf78f0c78c8b851256e80 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Tue, 17 Nov 2015 10:47:59 -0800 Subject: [PATCH] fix(router): apply APP_BASE_HREF when using PathLocationStrategy Correctly initializes APP_BASE_HREF, and falls back to the `` tag in the absence of an APP_BASE_HREF provider. Closes #5028 --- modules/angular2/router.ts | 4 +- .../src/mock/mock_location_strategy.ts | 11 +++- modules/angular2/src/router/location.ts | 59 ++----------------- .../angular2/src/router/location_strategy.ts | 35 +++++++++++ .../src/router/path_location_strategy.ts | 30 ++++++++-- modules/angular2/test/router/location_spec.ts | 13 +--- 6 files changed, 78 insertions(+), 74 deletions(-) diff --git a/modules/angular2/router.ts b/modules/angular2/router.ts index 64d65bcb11..ec8bd6bc8b 100644 --- a/modules/angular2/router.ts +++ b/modules/angular2/router.ts @@ -9,10 +9,10 @@ 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 {LocationStrategy} from './src/router/location_strategy'; +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'; -export {Location, APP_BASE_HREF} from './src/router/location'; +export {Location} from './src/router/location'; export * from './src/router/route_config_decorator'; export * from './src/router/route_definition'; export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/router/interfaces'; diff --git a/modules/angular2/src/mock/mock_location_strategy.ts b/modules/angular2/src/mock/mock_location_strategy.ts index b20b58c08f..2477d303f8 100644 --- a/modules/angular2/src/mock/mock_location_strategy.ts +++ b/modules/angular2/src/mock/mock_location_strategy.ts @@ -18,7 +18,12 @@ export class MockLocationStrategy extends LocationStrategy { path(): string { return this.internalPath; } - prepareExternalUrl(internal: string): string { return internal; } + prepareExternalUrl(internal: string): string { + if (internal.startsWith('/') && this.internalBaseHref.endsWith('/')) { + return this.internalBaseHref + internal.substring(1); + } + return this.internalBaseHref + internal; + } simulateUrlPop(pathname: string): void { ObservableWrapper.callNext(this._subject, {'url': pathname}); @@ -29,7 +34,9 @@ export class MockLocationStrategy extends LocationStrategy { var url = path + (query.length > 0 ? ('?' + query) : ''); this.internalPath = url; - this.urlChanges.push(url); + + var external = this.prepareExternalUrl(url); + this.urlChanges.push(external); } onPopState(fn: (value: any) => void): void { ObservableWrapper.subscribe(this._subject, fn); } diff --git a/modules/angular2/src/router/location.ts b/modules/angular2/src/router/location.ts index fb37309cb1..62749c1ee0 100644 --- a/modules/angular2/src/router/location.ts +++ b/modules/angular2/src/router/location.ts @@ -1,40 +1,6 @@ import {LocationStrategy} from './location_strategy'; -import {StringWrapper, isPresent, CONST_EXPR} from 'angular2/src/facade/lang'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; -import {isBlank} from 'angular2/src/facade/lang'; -import {BaseException, WrappedException} from 'angular2/src/facade/exceptions'; -import {OpaqueToken, Injectable, Optional, Inject} from 'angular2/angular2'; - -/** - * The `APP_BASE_HREF` token represents the base href to be used with the - * {@link PathLocationStrategy}. - * - * If you're using {@link PathLocationStrategy}, you must provide a provider to a string - * representing the URL prefix that should be preserved when generating and recognizing - * URLs. - * - * ### Example - * - * ``` - * import {Component} from 'angular2/angular2'; - * import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from 'angular2/router'; - * - * @Component({directives: [ROUTER_DIRECTIVES]}) - * @RouteConfig([ - * {...}, - * ]) - * class AppCmp { - * // ... - * } - * - * bootstrap(AppCmp, [ - * ROUTER_PROVIDERS, - * PathLocationStrategy, - * provide(APP_BASE_HREF, {useValue: '/my/app'}) - * ]); - * ``` - */ -export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHref')); +import {Injectable, Inject} from 'angular2/angular2'; /** * `Location` is a service that applications can use to interact with a browser's URL. @@ -83,15 +49,8 @@ export class Location { /** @internal */ _baseHref: string; - constructor(public platformStrategy: LocationStrategy, - @Optional() @Inject(APP_BASE_HREF) href?: string) { - var browserBaseHref = isPresent(href) ? href : this.platformStrategy.getBaseHref(); - - if (isBlank(browserBaseHref)) { - throw new BaseException( - `No base href set. Either provide a provider for the APP_BASE_HREF token or add a base element to the document.`); - } - + constructor(public platformStrategy: LocationStrategy) { + var browserBaseHref = this.platformStrategy.getBaseHref(); this._baseHref = stripTrailingSlash(stripIndexHtml(browserBaseHref)); this.platformStrategy.onPopState( (_) => { ObservableWrapper.callNext(this._subject, {'url': this.path(), 'pop': true}); }); @@ -117,11 +76,10 @@ export class Location { * used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use. */ prepareExternalUrl(url: string): string { - if (!url.startsWith('/')) { + if (url.length > 0 && !url.startsWith('/')) { url = '/' + url; } - return this.platformStrategy.prepareExternalUrl( - stripTrailingSlash(_addBaseHref(this._baseHref, url))); + return this.platformStrategy.prepareExternalUrl(url); } /** @@ -158,13 +116,6 @@ function _stripBaseHref(baseHref: string, url: string): string { return url; } -function _addBaseHref(baseHref: string, url: string): string { - if (!url.startsWith(baseHref)) { - return baseHref + url; - } - return url; -} - function stripIndexHtml(url: string): string { if (/\/index.html$/g.test(url)) { // '/index.html'.length == 11 diff --git a/modules/angular2/src/router/location_strategy.ts b/modules/angular2/src/router/location_strategy.ts index 311845bda4..0a06f89393 100644 --- a/modules/angular2/src/router/location_strategy.ts +++ b/modules/angular2/src/router/location_strategy.ts @@ -1,3 +1,6 @@ +import {CONST_EXPR} from 'angular2/src/facade/lang'; +import {OpaqueToken} from 'angular2/angular2'; + /** * `LocationStrategy` is responsible for representing and reading route state * from the the browser's URL. Angular provides two strategies: @@ -24,6 +27,38 @@ export abstract class LocationStrategy { abstract getBaseHref(): string; } + +/** + * The `APP_BASE_HREF` token represents the base href to be used with the + * {@link PathLocationStrategy}. + * + * If you're using {@link PathLocationStrategy}, you must provide a provider to a string + * representing the URL prefix that should be preserved when generating and recognizing + * URLs. + * + * ### Example + * + * ``` + * import {Component} from 'angular2/angular2'; + * import {ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig} from 'angular2/router'; + * + * @Component({directives: [ROUTER_DIRECTIVES]}) + * @RouteConfig([ + * {...}, + * ]) + * class AppCmp { + * // ... + * } + * + * bootstrap(AppCmp, [ + * ROUTER_PROVIDERS, + * PathLocationStrategy, + * provide(APP_BASE_HREF, {useValue: '/my/app'}) + * ]); + * ``` + */ +export const APP_BASE_HREF: OpaqueToken = CONST_EXPR(new OpaqueToken('appBaseHref')); + export function normalizeQueryParams(params: string): string { return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params; } diff --git a/modules/angular2/src/router/path_location_strategy.ts b/modules/angular2/src/router/path_location_strategy.ts index 0032f8f3dc..4504058353 100644 --- a/modules/angular2/src/router/path_location_strategy.ts +++ b/modules/angular2/src/router/path_location_strategy.ts @@ -1,7 +1,9 @@ import {DOM} from 'angular2/src/core/dom/dom_adapter'; -import {Injectable} from 'angular2/angular2'; +import {Injectable, Inject} from 'angular2/angular2'; import {EventListener, History, Location} from 'angular2/src/facade/browser'; -import {LocationStrategy, normalizeQueryParams} from './location_strategy'; +import {isBlank} from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; +import {LocationStrategy, APP_BASE_HREF, normalizeQueryParams} from './location_strategy'; /** * `PathLocationStrategy` is a {@link LocationStrategy} used to configure the @@ -54,11 +56,21 @@ export class PathLocationStrategy extends LocationStrategy { private _history: History; private _baseHref: string; - constructor() { + constructor(@Inject(APP_BASE_HREF) href?: string) { super(); + + if (isBlank(href)) { + href = DOM.getBaseHref(); + } + + 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 = DOM.getBaseHref(); + this._baseHref = href; } onPopState(fn: EventListener): void { @@ -68,12 +80,18 @@ export class PathLocationStrategy extends LocationStrategy { getBaseHref(): string { return this._baseHref; } - prepareExternalUrl(internal: string): string { return this._baseHref + internal; } + prepareExternalUrl(internal: string): string { + if (internal.startsWith('/') && this._baseHref.endsWith('/')) { + return this._baseHref + internal.substring(1); + } + return this._baseHref + internal; + } path(): string { return this._location.pathname + normalizeQueryParams(this._location.search); } pushState(state: any, title: string, url: string, queryParams: string) { - this._history.pushState(state, title, (url + normalizeQueryParams(queryParams))); + var externalUrl = this.prepareExternalUrl(url + normalizeQueryParams(queryParams)); + this._history.pushState(state, title, externalUrl); } forward(): void { this._history.forward(); } diff --git a/modules/angular2/test/router/location_spec.ts b/modules/angular2/test/router/location_spec.ts index bb9c3d4499..1a587e9ccb 100644 --- a/modules/angular2/test/router/location_spec.ts +++ b/modules/angular2/test/router/location_spec.ts @@ -14,8 +14,9 @@ import { import {Injector, provide} from 'angular2/core'; import {CONST_EXPR} from 'angular2/src/facade/lang'; -import {Location, APP_BASE_HREF} from 'angular2/src/router/location'; -import {LocationStrategy} from 'angular2/src/router/location_strategy'; + +import {Location} from 'angular2/src/router/location'; +import {LocationStrategy, APP_BASE_HREF} from 'angular2/src/router/location_strategy'; import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy'; export function main() { @@ -54,14 +55,6 @@ export function main() { }) })); - it('should throw when no base href is provided', () => { - var locationStrategy = new MockLocationStrategy(); - locationStrategy.internalBaseHref = null; - expect(() => new Location(locationStrategy)) - .toThrowError( - `No base href set. Either provide a provider for the APP_BASE_HREF token or add a base element to the document.`); - }); - it('should revert to the previous path when a back() operation is executed', () => { var locationStrategy = new MockLocationStrategy(); var location = new Location(locationStrategy);