fix(router): apply APP_BASE_HREF when using PathLocationStrategy

Correctly initializes APP_BASE_HREF, and falls back to the `<base>` tag in the absence
of an APP_BASE_HREF provider.

Closes #5028
This commit is contained in:
Brian Ford 2015-11-17 10:47:59 -08:00
parent b571baab68
commit ac38812809
6 changed files with 78 additions and 74 deletions

View File

@ -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';

View File

@ -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); }

View File

@ -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

View File

@ -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;
}

View File

@ -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(); }

View File

@ -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);