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

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