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:
parent
b571baab68
commit
ac38812809
@ -9,10 +9,10 @@ export {RouterOutlet} from './src/router/router_outlet';
|
|||||||
export {RouterLink} from './src/router/router_link';
|
export {RouterLink} from './src/router/router_link';
|
||||||
export {RouteParams, RouteData} from './src/router/instruction';
|
export {RouteParams, RouteData} from './src/router/instruction';
|
||||||
export {RouteRegistry} from './src/router/route_registry';
|
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 {HashLocationStrategy} from './src/router/hash_location_strategy';
|
||||||
export {PathLocationStrategy} from './src/router/path_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_config_decorator';
|
||||||
export * from './src/router/route_definition';
|
export * from './src/router/route_definition';
|
||||||
export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/router/interfaces';
|
export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/router/interfaces';
|
||||||
|
@ -18,7 +18,12 @@ export class MockLocationStrategy extends LocationStrategy {
|
|||||||
|
|
||||||
path(): string { return this.internalPath; }
|
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 {
|
simulateUrlPop(pathname: string): void {
|
||||||
ObservableWrapper.callNext(this._subject, {'url': pathname});
|
ObservableWrapper.callNext(this._subject, {'url': pathname});
|
||||||
@ -29,7 +34,9 @@ export class MockLocationStrategy extends LocationStrategy {
|
|||||||
|
|
||||||
var url = path + (query.length > 0 ? ('?' + query) : '');
|
var url = path + (query.length > 0 ? ('?' + query) : '');
|
||||||
this.internalPath = url;
|
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); }
|
onPopState(fn: (value: any) => void): void { ObservableWrapper.subscribe(this._subject, fn); }
|
||||||
|
@ -1,40 +1,6 @@
|
|||||||
import {LocationStrategy} from './location_strategy';
|
import {LocationStrategy} from './location_strategy';
|
||||||
import {StringWrapper, isPresent, CONST_EXPR} from 'angular2/src/facade/lang';
|
|
||||||
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||||
import {isBlank} from 'angular2/src/facade/lang';
|
import {Injectable, Inject} from 'angular2/angular2';
|
||||||
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'));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Location` is a service that applications can use to interact with a browser's URL.
|
* `Location` is a service that applications can use to interact with a browser's URL.
|
||||||
@ -83,15 +49,8 @@ export class Location {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_baseHref: string;
|
_baseHref: string;
|
||||||
|
|
||||||
constructor(public platformStrategy: LocationStrategy,
|
constructor(public platformStrategy: LocationStrategy) {
|
||||||
@Optional() @Inject(APP_BASE_HREF) href?: string) {
|
var browserBaseHref = this.platformStrategy.getBaseHref();
|
||||||
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.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._baseHref = stripTrailingSlash(stripIndexHtml(browserBaseHref));
|
this._baseHref = stripTrailingSlash(stripIndexHtml(browserBaseHref));
|
||||||
this.platformStrategy.onPopState(
|
this.platformStrategy.onPopState(
|
||||||
(_) => { ObservableWrapper.callNext(this._subject, {'url': this.path(), 'pop': true}); });
|
(_) => { 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.
|
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
|
||||||
*/
|
*/
|
||||||
prepareExternalUrl(url: string): string {
|
prepareExternalUrl(url: string): string {
|
||||||
if (!url.startsWith('/')) {
|
if (url.length > 0 && !url.startsWith('/')) {
|
||||||
url = '/' + url;
|
url = '/' + url;
|
||||||
}
|
}
|
||||||
return this.platformStrategy.prepareExternalUrl(
|
return this.platformStrategy.prepareExternalUrl(url);
|
||||||
stripTrailingSlash(_addBaseHref(this._baseHref, url)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,13 +116,6 @@ function _stripBaseHref(baseHref: string, url: string): string {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _addBaseHref(baseHref: string, url: string): string {
|
|
||||||
if (!url.startsWith(baseHref)) {
|
|
||||||
return baseHref + url;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripIndexHtml(url: string): string {
|
function stripIndexHtml(url: string): string {
|
||||||
if (/\/index.html$/g.test(url)) {
|
if (/\/index.html$/g.test(url)) {
|
||||||
// '/index.html'.length == 11
|
// '/index.html'.length == 11
|
||||||
|
@ -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
|
* `LocationStrategy` is responsible for representing and reading route state
|
||||||
* from the the browser's URL. Angular provides two strategies:
|
* from the the browser's URL. Angular provides two strategies:
|
||||||
@ -24,6 +27,38 @@ export abstract class LocationStrategy {
|
|||||||
abstract getBaseHref(): string;
|
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 {
|
export function normalizeQueryParams(params: string): string {
|
||||||
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
|
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
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 {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
|
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||||
@ -54,11 +56,21 @@ export class PathLocationStrategy extends LocationStrategy {
|
|||||||
private _history: History;
|
private _history: History;
|
||||||
private _baseHref: string;
|
private _baseHref: string;
|
||||||
|
|
||||||
constructor() {
|
constructor(@Inject(APP_BASE_HREF) href?: string) {
|
||||||
super();
|
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._location = DOM.getLocation();
|
||||||
this._history = DOM.getHistory();
|
this._history = DOM.getHistory();
|
||||||
this._baseHref = DOM.getBaseHref();
|
this._baseHref = href;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPopState(fn: EventListener): void {
|
onPopState(fn: EventListener): void {
|
||||||
@ -68,12 +80,18 @@ export class PathLocationStrategy extends LocationStrategy {
|
|||||||
|
|
||||||
getBaseHref(): string { return this._baseHref; }
|
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); }
|
path(): string { return this._location.pathname + normalizeQueryParams(this._location.search); }
|
||||||
|
|
||||||
pushState(state: any, title: string, url: string, queryParams: string) {
|
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(); }
|
forward(): void { this._history.forward(); }
|
||||||
|
@ -14,8 +14,9 @@ import {
|
|||||||
|
|
||||||
import {Injector, provide} from 'angular2/core';
|
import {Injector, provide} from 'angular2/core';
|
||||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
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';
|
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
|
||||||
|
|
||||||
export function main() {
|
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', () => {
|
it('should revert to the previous path when a back() operation is executed', () => {
|
||||||
var locationStrategy = new MockLocationStrategy();
|
var locationStrategy = new MockLocationStrategy();
|
||||||
var location = new Location(locationStrategy);
|
var location = new Location(locationStrategy);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user