Skip to content

Commit

Permalink
fix(state): When creating absolute hrefs in hashbang mode, include th…
Browse files Browse the repository at this point in the history
…e location.pathname

Closes #3710
  • Loading branch information
christopherthielen committed Jun 20, 2018
1 parent fe91bd3 commit cd426e5
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 14 deletions.
20 changes: 13 additions & 7 deletions src/locationServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
*/ /** */
import { LocationConfig, LocationServices, UIRouter, ParamType, isDefined } from '@uirouter/core';
import { val, createProxyFunctions, removeFrom, isObject } from '@uirouter/core';
import { ILocationService, ILocationProvider } from 'angular';
import { ILocationService, ILocationProvider, IWindowService } from 'angular';

/**
* Implements UI-Router LocationServices and LocationConfig using Angular 1's $location service
*/
export class Ng1LocationServices implements LocationConfig, LocationServices {
private $locationProvider: ILocationProvider;
private $location: ILocationService;
private $sniffer;
private $sniffer: any;
private $browser: any;
private $window: IWindowService;

path;
search;
Expand All @@ -21,7 +23,8 @@ export class Ng1LocationServices implements LocationConfig, LocationServices {
port;
protocol;
host;
baseHref;

private _baseHref: string;

// .onChange() registry
private _urlListeners: Function[] = [];
Expand Down Expand Up @@ -67,27 +70,30 @@ export class Ng1LocationServices implements LocationConfig, LocationServices {
return html5Mode && this.$sniffer.history;
}

baseHref() {
return this._baseHref || (this._baseHref = this.$browser.baseHref() || this.$window.location.pathname);
}

url(newUrl?: string, replace = false, state?) {
if (isDefined(newUrl)) this.$location.url(newUrl);
if (replace) this.$location.replace();
if (state) this.$location.state(state);
return this.$location.url();
}

_runtimeServices($rootScope, $location: ILocationService, $sniffer, $browser) {
_runtimeServices($rootScope, $location: ILocationService, $sniffer, $browser, $window: IWindowService) {
this.$location = $location;
this.$sniffer = $sniffer;
this.$browser = $browser;
this.$window = $window;

// Bind $locationChangeSuccess to the listeners registered in LocationService.onChange
$rootScope.$on('$locationChangeSuccess', evt => this._urlListeners.forEach(fn => fn(evt)));
const _loc = val($location);
const _browser = val($browser);

// Bind these LocationService functions to $location
createProxyFunctions(_loc, this, _loc, ['replace', 'path', 'search', 'hash']);
// Bind these LocationConfig functions to $location
createProxyFunctions(_loc, this, _loc, ['port', 'protocol', 'host']);
// Bind these LocationConfig functions to $browser
createProxyFunctions(_browser, this, _browser, ['baseHref']);
}
}
5 changes: 3 additions & 2 deletions src/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,17 @@ function $uiRouterProvider($locationProvider: ILocationProvider) {
// backwards compat: also expose router instance as $uiRouterProvider.router
router['router'] = router;
router['$get'] = $get;
$get.$inject = ['$location', '$browser', '$sniffer', '$rootScope', '$http', '$templateCache'];
$get.$inject = ['$location', '$browser', '$window', '$sniffer', '$rootScope', '$http', '$templateCache'];
function $get(
$location: ILocationService,
$browser: any,
$window: any,
$sniffer: any,
$rootScope: ng.IScope,
$http: IHttpService,
$templateCache: ITemplateCacheService
) {
ng1LocationService._runtimeServices($rootScope, $location, $sniffer, $browser);
ng1LocationService._runtimeServices($rootScope, $location, $sniffer, $browser, $window);
delete router['router'];
delete router['$get'];
return router;
Expand Down
39 changes: 34 additions & 5 deletions test/stateSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1211,11 +1211,40 @@ describe('state', function() {
expect($state.href('root', {}, { inherit: true })).toEqual('#/root?param1=1');
}));

it('generates absolute url when absolute is true', inject(function($state) {
expect($state.href('about.sidebar', null, { absolute: true })).toEqual('http://server/#/about');
locationProvider.html5Mode(true);
expect($state.href('about.sidebar', null, { absolute: true })).toEqual('http://server/about');
}));
describe('generates an absolute url', () => {
describe('when html5mode is false', () => {
it('and absolute is true', inject(function($state, $window) {
const pathname = $window.location.pathname;
$window.history.replaceState(null, '', '/');
expect($window.location.pathname).toBe('/');
expect($state.href('about.sidebar', null, { absolute: true })).toEqual('http://server/#/about');
$window.history.replaceState(null, '', pathname);
}));

it('and absolute is true and a base tag is present', inject(function($state, $window, $browser) {
spyOn($browser, 'baseHref').and.returnValue('/nested/path');
expect($state.href('about.sidebar', null, { absolute: true })).toEqual('http://server/nested/path#/about');
}));

it('and absolute is true and the app is served from a nested document root', inject(function(
$state,
$window,
$browser
) {
const pathname = $window.location.pathname;
$window.history.replaceState(null, 'nested path', '/nested/path');
expect($window.location.pathname).toBe('/nested/path');
spyOn($browser, 'baseHref').and.returnValue(null);
expect($state.href('about.sidebar', null, { absolute: true })).toEqual('http://server/nested/path#/about');
$window.history.replaceState(null, '', pathname);
}));
});

it('when html5Mode is true', inject(function($state) {
locationProvider.html5Mode(true);
expect($state.href('about.sidebar', null, { absolute: true })).toEqual('http://server/about');
}));
});

it('respects $locationProvider.hashPrefix()', inject(function($state) {
locationProvider.hashPrefix('!');
Expand Down

0 comments on commit cd426e5

Please sign in to comment.