fix(router): properly read and serialize query params
This splits out `path` and `query` into separate params for `location.go` and related methods so that we can handle them properly in both `PathLocationStrategy` and `HashLocationStrategy`. This handles the problem of not reading query params to populate `Location` on the initial page load. Closes #3957 Closes #4225 Closes #3784
This commit is contained in:
parent
440fd11c72
commit
8bc40d3f4d
|
@ -7,6 +7,8 @@ export class SpyLocation implements Location {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_path: string = '';
|
_path: string = '';
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
_query: string = '';
|
||||||
|
/** @internal */
|
||||||
_subject: EventEmitter = new EventEmitter();
|
_subject: EventEmitter = new EventEmitter();
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_baseHref: string = '';
|
_baseHref: string = '';
|
||||||
|
@ -21,12 +23,15 @@ export class SpyLocation implements Location {
|
||||||
|
|
||||||
normalizeAbsolutely(url: string): string { return this._baseHref + url; }
|
normalizeAbsolutely(url: string): string { return this._baseHref + url; }
|
||||||
|
|
||||||
go(url: string) {
|
go(path: string, query: string = '') {
|
||||||
url = this.normalizeAbsolutely(url);
|
path = this.normalizeAbsolutely(path);
|
||||||
if (this._path == url) {
|
if (this._path == path && this._query == query) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._path = url;
|
this._path = path;
|
||||||
|
this._query = query;
|
||||||
|
|
||||||
|
var url = path + (query.length > 0 ? ('?' + query) : '');
|
||||||
this.urlChanges.push(url);
|
this.urlChanges.push(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,10 @@ export class MockLocationStrategy extends LocationStrategy {
|
||||||
ObservableWrapper.callNext(this._subject, {'url': pathname});
|
ObservableWrapper.callNext(this._subject, {'url': pathname});
|
||||||
}
|
}
|
||||||
|
|
||||||
pushState(ctx: any, title: string, url: string): void {
|
pushState(ctx: any, title: string, path: string, query: string): void {
|
||||||
this.internalTitle = title;
|
this.internalTitle = title;
|
||||||
|
|
||||||
|
var url = path + (query.length > 0 ? ('?' + query) : '');
|
||||||
this.internalPath = url;
|
this.internalPath = url;
|
||||||
this.urlChanges.push(url);
|
this.urlChanges.push(url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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} from 'angular2/angular2';
|
||||||
import {LocationStrategy} from './location_strategy';
|
import {LocationStrategy, normalizeQueryParams} from './location_strategy';
|
||||||
import {EventListener, History, Location} from 'angular2/src/core/facade/browser';
|
import {EventListener, History, Location} from 'angular2/src/core/facade/browser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,11 +61,18 @@ export class HashLocationStrategy extends LocationStrategy {
|
||||||
// Dart will complain if a call to substring is
|
// Dart will complain if a call to substring is
|
||||||
// executed with a position value that extends the
|
// executed with a position value that extends the
|
||||||
// length of string.
|
// length of string.
|
||||||
return path.length > 0 ? path.substring(1) : path;
|
return (path.length > 0 ? path.substring(1) : path) +
|
||||||
|
normalizeQueryParams(this._location.search);
|
||||||
}
|
}
|
||||||
|
|
||||||
pushState(state: any, title: string, url: string) {
|
pushState(state: any, title: string, path: string, queryParams: string) {
|
||||||
this._history.pushState(state, title, '#' + url);
|
var url = path + normalizeQueryParams(queryParams);
|
||||||
|
if (url.length == 0) {
|
||||||
|
url = this._location.pathname;
|
||||||
|
} else {
|
||||||
|
url = '#' + url;
|
||||||
|
}
|
||||||
|
this._history.pushState(state, title, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
forward(): void { this._history.forward(); }
|
forward(): void { this._history.forward(); }
|
||||||
|
|
|
@ -94,12 +94,18 @@ export class PrimaryInstruction {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringifyInstruction(instruction: Instruction): string {
|
export function stringifyInstruction(instruction: Instruction): string {
|
||||||
var params = instruction.component.urlParams.length > 0 ?
|
return stringifyInstructionPath(instruction) + stringifyInstructionQuery(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringifyInstructionPath(instruction: Instruction): string {
|
||||||
|
return instruction.component.urlPath + stringifyAux(instruction) +
|
||||||
|
stringifyPrimary(instruction.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringifyInstructionQuery(instruction: Instruction): string {
|
||||||
|
return instruction.component.urlParams.length > 0 ?
|
||||||
('?' + instruction.component.urlParams.join('&')) :
|
('?' + instruction.component.urlParams.join('&')) :
|
||||||
'';
|
'';
|
||||||
|
|
||||||
return instruction.component.urlPath + stringifyAux(instruction) +
|
|
||||||
stringifyPrimary(instruction.child) + params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyPrimary(instruction: Instruction): string {
|
function stringifyPrimary(instruction: Instruction): string {
|
||||||
|
|
|
@ -125,9 +125,9 @@ export class Location {
|
||||||
* Changes the browsers URL to the normalized version of the given URL, and pushes a
|
* Changes the browsers URL to the normalized version of the given URL, and pushes a
|
||||||
* new item onto the platform's history.
|
* new item onto the platform's history.
|
||||||
*/
|
*/
|
||||||
go(url: string): void {
|
go(path: string, query: string = ''): void {
|
||||||
var finalUrl = this.normalizeAbsolutely(url);
|
var absolutePath = this.normalizeAbsolutely(path);
|
||||||
this.platformStrategy.pushState(null, '', finalUrl);
|
this.platformStrategy.pushState(null, '', absolutePath, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
*/
|
*/
|
||||||
export abstract class LocationStrategy {
|
export abstract class LocationStrategy {
|
||||||
abstract path(): string;
|
abstract path(): string;
|
||||||
abstract pushState(ctx: any, title: string, url: string): void;
|
abstract pushState(state: any, title: string, url: string, queryParams: string): void;
|
||||||
abstract forward(): void;
|
abstract forward(): void;
|
||||||
abstract back(): void;
|
abstract back(): void;
|
||||||
abstract onPopState(fn: (_: any) => any): void;
|
abstract onPopState(fn: (_: any) => any): void;
|
||||||
abstract getBaseHref(): string;
|
abstract getBaseHref(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeQueryParams(params: string): string {
|
||||||
|
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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} from 'angular2/angular2';
|
||||||
import {EventListener, History, Location} from 'angular2/src/core/facade/browser';
|
import {EventListener, History, Location} from 'angular2/src/core/facade/browser';
|
||||||
import {LocationStrategy} from './location_strategy';
|
import {LocationStrategy, normalizeQueryParams} from './location_strategy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||||
|
@ -67,9 +67,11 @@ export class PathLocationStrategy extends LocationStrategy {
|
||||||
|
|
||||||
getBaseHref(): string { return this._baseHref; }
|
getBaseHref(): string { return this._baseHref; }
|
||||||
|
|
||||||
path(): string { return this._location.pathname; }
|
path(): string { return this._location.pathname + normalizeQueryParams(this._location.search); }
|
||||||
|
|
||||||
pushState(state: any, title: string, url: string) { this._history.pushState(state, title, url); }
|
pushState(state: any, title: string, url: string, queryParams: string) {
|
||||||
|
this._history.pushState(state, title, (url + normalizeQueryParams(queryParams)));
|
||||||
|
}
|
||||||
|
|
||||||
forward(): void { this._history.forward(); }
|
forward(): void { this._history.forward(); }
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,13 @@ import {
|
||||||
} from 'angular2/src/core/facade/lang';
|
} from 'angular2/src/core/facade/lang';
|
||||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||||
import {RouteRegistry} from './route_registry';
|
import {RouteRegistry} from './route_registry';
|
||||||
import {ComponentInstruction, Instruction, stringifyInstruction} from './instruction';
|
import {
|
||||||
|
ComponentInstruction,
|
||||||
|
Instruction,
|
||||||
|
stringifyInstruction,
|
||||||
|
stringifyInstructionPath,
|
||||||
|
stringifyInstructionQuery
|
||||||
|
} from './instruction';
|
||||||
import {RouterOutlet} from './router_outlet';
|
import {RouterOutlet} from './router_outlet';
|
||||||
import {Location} from './location';
|
import {Location} from './location';
|
||||||
import {getCanActivateHook} from './route_lifecycle_reflector';
|
import {getCanActivateHook} from './route_lifecycle_reflector';
|
||||||
|
@ -472,13 +478,14 @@ export class RootRouter extends Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
|
||||||
var emitUrl = stringifyInstruction(instruction);
|
var emitPath = stringifyInstructionPath(instruction);
|
||||||
if (emitUrl.length > 0) {
|
var emitQuery = stringifyInstructionQuery(instruction);
|
||||||
emitUrl = '/' + emitUrl;
|
if (emitPath.length > 0) {
|
||||||
|
emitPath = '/' + emitPath;
|
||||||
}
|
}
|
||||||
var promise = super.commit(instruction);
|
var promise = super.commit(instruction);
|
||||||
if (!_skipLocationChange) {
|
if (!_skipLocationChange) {
|
||||||
promise = promise.then((_) => { this._location.go(emitUrl); });
|
promise = promise.then((_) => { this._location.go(emitPath, emitQuery); });
|
||||||
}
|
}
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,10 @@ export function pathSegmentsToUrl(pathSegments: string[]): Url {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&]+');
|
var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&#]+');
|
||||||
function matchUrlSegment(str: string): string {
|
function matchUrlSegment(str: string): string {
|
||||||
var match = RegExpWrapper.firstMatch(SEGMENT_RE, str);
|
var match = RegExpWrapper.firstMatch(SEGMENT_RE, str);
|
||||||
return isPresent(match) ? match[0] : null;
|
return isPresent(match) ? match[0] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UrlParser {
|
export class UrlParser {
|
||||||
|
|
Loading…
Reference in New Issue