Skip to content

Commit

Permalink
fix(router): apply APP_BASE_HREF when using PathLocationStrategy
Browse files Browse the repository at this point in the history
Correctly initializes APP_BASE_HREF, and falls back to the `<base>` tag in the absence
of an APP_BASE_HREF provider.

Closes #5028
  • Loading branch information
btford committed Nov 17, 2015
1 parent b571baa commit ac38812
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 74 deletions.
4 changes: 2 additions & 2 deletions modules/angular2/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
11 changes: 9 additions & 2 deletions modules/angular2/src/mock/mock_location_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand All @@ -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); }
Expand Down
59 changes: 5 additions & 54 deletions modules/angular2/src/router/location.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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}); });
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions modules/angular2/src/router/location_strategy.ts
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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;
}
30 changes: 24 additions & 6 deletions modules/angular2/src/router/path_location_strategy.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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(); }
Expand Down
13 changes: 3 additions & 10 deletions modules/angular2/test/router/location_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit ac38812

Please sign in to comment.