feat(router): add queryParams and fragment to every activated route

This commit is contained in:
vsavkin 2016-08-02 15:31:56 -07:00 committed by Alex Rickabaugh
parent 550ab31bd0
commit 422d380b3e
8 changed files with 192 additions and 181 deletions

View File

@ -13,9 +13,7 @@ import {TreeNode} from './utils/tree';
export function createRouterState(curr: RouterStateSnapshot, prevState: RouterState): RouterState { export function createRouterState(curr: RouterStateSnapshot, prevState: RouterState): RouterState {
const root = createNode(curr._root, prevState ? prevState._root : undefined); const root = createNode(curr._root, prevState ? prevState._root : undefined);
const queryParams = prevState ? prevState.queryParams : new BehaviorSubject(curr.queryParams); return new RouterState(root, curr);
const fragment = prevState ? prevState.fragment : new BehaviorSubject(curr.fragment);
return new RouterState(root, queryParams, fragment, curr);
} }
function createNode(curr: TreeNode<ActivatedRouteSnapshot>, prevState?: TreeNode<ActivatedRoute>): function createNode(curr: TreeNode<ActivatedRouteSnapshot>, prevState?: TreeNode<ActivatedRoute>):
@ -48,8 +46,8 @@ function createOrReuseChildren(
function createActivatedRoute(c: ActivatedRouteSnapshot) { function createActivatedRoute(c: ActivatedRouteSnapshot) {
return new ActivatedRoute( return new ActivatedRoute(
new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.data), new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams),
c.outlet, c.component, c); new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
} }
function equalRouteSnapshots(a: ActivatedRouteSnapshot, b: ActivatedRouteSnapshot): boolean { function equalRouteSnapshots(a: ActivatedRouteSnapshot, b: ActivatedRouteSnapshot): boolean {

View File

@ -40,47 +40,131 @@ class InheritedFromParent {
export function recognize(rootComponentType: Type, config: Routes, urlTree: UrlTree, url: string): export function recognize(rootComponentType: Type, config: Routes, urlTree: UrlTree, url: string):
Observable<RouterStateSnapshot> { Observable<RouterStateSnapshot> {
try { return new Recognizer(rootComponentType, config, urlTree, url).recognize();
const rootSegmentGroup = split(urlTree.root, [], [], config).segmentGroup; }
const children = processSegmentGroup(
config, rootSegmentGroup, InheritedFromParent.empty(null), PRIMARY_OUTLET); class Recognizer {
const root = new ActivatedRouteSnapshot( constructor(
[], Object.freeze({}), {}, PRIMARY_OUTLET, rootComponentType, null, urlTree.root, -1, private rootComponentType: Type, private config: Routes, private urlTree: UrlTree,
InheritedResolve.empty); private url: string) {}
const rootNode = new TreeNode<ActivatedRouteSnapshot>(root, children);
return of (new RouterStateSnapshot( recognize(): Observable<RouterStateSnapshot> {
url, rootNode, Object.freeze(urlTree.queryParams), urlTree.fragment)); try {
} catch (e) { const rootSegmentGroup = split(this.urlTree.root, [], [], this.config).segmentGroup;
if (e instanceof NoMatch) {
return new Observable<RouterStateSnapshot>( const children = this.processSegmentGroup(
(obs: Observer<RouterStateSnapshot>) => this.config, rootSegmentGroup, InheritedFromParent.empty(null), PRIMARY_OUTLET);
obs.error(new Error(`Cannot match any routes: '${e.segmentGroup}'`)));
} else { const root = new ActivatedRouteSnapshot(
return new Observable<RouterStateSnapshot>( [], Object.freeze({}), Object.freeze(this.urlTree.queryParams), this.urlTree.fragment, {},
(obs: Observer<RouterStateSnapshot>) => obs.error(e)); PRIMARY_OUTLET, this.rootComponentType, null, this.urlTree.root, -1,
InheritedResolve.empty);
const rootNode = new TreeNode<ActivatedRouteSnapshot>(root, children);
return of (new RouterStateSnapshot(this.url, rootNode));
} catch (e) {
if (e instanceof NoMatch) {
return new Observable<RouterStateSnapshot>(
(obs: Observer<RouterStateSnapshot>) =>
obs.error(new Error(`Cannot match any routes: '${e.segmentGroup}'`)));
} else {
return new Observable<RouterStateSnapshot>(
(obs: Observer<RouterStateSnapshot>) => obs.error(e));
}
} }
} }
}
function processSegmentGroup(
config: Route[], segmentGroup: UrlSegmentGroup, inherited: InheritedFromParent, processSegmentGroup(
outlet: string): TreeNode<ActivatedRouteSnapshot>[] { config: Route[], segmentGroup: UrlSegmentGroup, inherited: InheritedFromParent,
if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) { outlet: string): TreeNode<ActivatedRouteSnapshot>[] {
return processChildren(config, segmentGroup, inherited); if (segmentGroup.segments.length === 0 && segmentGroup.hasChildren()) {
} else { return this.processChildren(config, segmentGroup, inherited);
return processSegment(config, segmentGroup, 0, segmentGroup.segments, inherited, outlet); } else {
return this.processSegment(config, segmentGroup, 0, segmentGroup.segments, inherited, outlet);
}
} }
}
function processChildren( processChildren(config: Route[], segmentGroup: UrlSegmentGroup, inherited: InheritedFromParent):
config: Route[], segmentGroup: UrlSegmentGroup, TreeNode<ActivatedRouteSnapshot>[] {
inherited: InheritedFromParent): TreeNode<ActivatedRouteSnapshot>[] { const children = mapChildrenIntoArray(
const children = mapChildrenIntoArray( segmentGroup,
segmentGroup, (child, childOutlet) => this.processSegmentGroup(config, child, inherited, childOutlet));
(child, childOutlet) => processSegmentGroup(config, child, inherited, childOutlet)); checkOutletNameUniqueness(children);
checkOutletNameUniqueness(children); sortActivatedRouteSnapshots(children);
sortActivatedRouteSnapshots(children); return children;
return children; }
processSegment(
config: Route[], segmentGroup: UrlSegmentGroup, pathIndex: number, segments: UrlSegment[],
inherited: InheritedFromParent, outlet: string): TreeNode<ActivatedRouteSnapshot>[] {
for (let r of config) {
try {
return this.processSegmentAgainstRoute(
r, segmentGroup, pathIndex, segments, inherited, outlet);
} catch (e) {
if (!(e instanceof NoMatch)) throw e;
}
}
throw new NoMatch(segmentGroup);
}
processSegmentAgainstRoute(
route: Route, rawSegment: UrlSegmentGroup, pathIndex: number, segments: UrlSegment[],
inherited: InheritedFromParent, outlet: string): TreeNode<ActivatedRouteSnapshot>[] {
if (route.redirectTo) throw new NoMatch();
if ((route.outlet ? route.outlet : PRIMARY_OUTLET) !== outlet) throw new NoMatch();
const newInheritedResolve = new InheritedResolve(inherited.resolve, getResolve(route));
if (route.path === '**') {
const params = segments.length > 0 ? last(segments).parameters : {};
const snapshot = new ActivatedRouteSnapshot(
segments, Object.freeze(merge(inherited.allParams, params)),
Object.freeze(this.urlTree.queryParams), this.urlTree.fragment,
merge(inherited.allData, getData(route)), outlet, route.component, route,
getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + segments.length,
newInheritedResolve);
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, [])];
}
const {consumedSegments, parameters, lastChild} =
match(rawSegment, route, segments, inherited.snapshot);
const rawSlicedSegments = segments.slice(lastChild);
const childConfig = getChildConfig(route);
const {segmentGroup, slicedSegments} =
split(rawSegment, consumedSegments, rawSlicedSegments, childConfig);
const snapshot = new ActivatedRouteSnapshot(
consumedSegments, Object.freeze(merge(inherited.allParams, parameters)),
Object.freeze(this.urlTree.queryParams), this.urlTree.fragment,
merge(inherited.allData, getData(route)), outlet, route.component, route,
getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length,
newInheritedResolve);
const newInherited = route.component ?
InheritedFromParent.empty(snapshot) :
new InheritedFromParent(
inherited, snapshot, parameters, getData(route), newInheritedResolve);
if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
const children = this.processChildren(childConfig, segmentGroup, newInherited);
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, children)];
} else if (childConfig.length === 0 && slicedSegments.length === 0) {
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, [])];
} else {
const children = this.processSegment(
childConfig, segmentGroup, pathIndex + lastChild, slicedSegments, newInherited,
PRIMARY_OUTLET);
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, children)];
}
}
} }
function sortActivatedRouteSnapshots(nodes: TreeNode<ActivatedRouteSnapshot>[]): void { function sortActivatedRouteSnapshots(nodes: TreeNode<ActivatedRouteSnapshot>[]): void {
@ -91,71 +175,6 @@ function sortActivatedRouteSnapshots(nodes: TreeNode<ActivatedRouteSnapshot>[]):
}); });
} }
function processSegment(
config: Route[], segmentGroup: UrlSegmentGroup, pathIndex: number, segments: UrlSegment[],
inherited: InheritedFromParent, outlet: string): TreeNode<ActivatedRouteSnapshot>[] {
for (let r of config) {
try {
return processSegmentAgainstRoute(r, segmentGroup, pathIndex, segments, inherited, outlet);
} catch (e) {
if (!(e instanceof NoMatch)) throw e;
}
}
throw new NoMatch(segmentGroup);
}
function processSegmentAgainstRoute(
route: Route, rawSegment: UrlSegmentGroup, pathIndex: number, segments: UrlSegment[],
inherited: InheritedFromParent, outlet: string): TreeNode<ActivatedRouteSnapshot>[] {
if (route.redirectTo) throw new NoMatch();
if ((route.outlet ? route.outlet : PRIMARY_OUTLET) !== outlet) throw new NoMatch();
const newInheritedResolve = new InheritedResolve(inherited.resolve, getResolve(route));
if (route.path === '**') {
const params = segments.length > 0 ? last(segments).parameters : {};
const snapshot = new ActivatedRouteSnapshot(
segments, Object.freeze(merge(inherited.allParams, params)),
merge(inherited.allData, getData(route)), outlet, route.component, route,
getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + segments.length,
newInheritedResolve);
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, [])];
}
const {consumedSegments, parameters, lastChild} =
match(rawSegment, route, segments, inherited.snapshot);
const rawSlicedSegments = segments.slice(lastChild);
const childConfig = getChildConfig(route);
const {segmentGroup, slicedSegments} =
split(rawSegment, consumedSegments, rawSlicedSegments, childConfig);
const snapshot = new ActivatedRouteSnapshot(
consumedSegments, Object.freeze(merge(inherited.allParams, parameters)),
merge(inherited.allData, getData(route)), outlet, route.component, route,
getSourceSegmentGroup(rawSegment), getPathIndexShift(rawSegment) + consumedSegments.length,
newInheritedResolve);
const newInherited = route.component ?
InheritedFromParent.empty(snapshot) :
new InheritedFromParent(inherited, snapshot, parameters, getData(route), newInheritedResolve);
if (slicedSegments.length === 0 && segmentGroup.hasChildren()) {
const children = processChildren(childConfig, segmentGroup, newInherited);
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, children)];
} else if (childConfig.length === 0 && slicedSegments.length === 0) {
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, [])];
} else {
const children = processSegment(
childConfig, segmentGroup, pathIndex + lastChild, slicedSegments, newInherited,
PRIMARY_OUTLET);
return [new TreeNode<ActivatedRouteSnapshot>(snapshot, children)];
}
}
function getChildConfig(route: Route): Route[] { function getChildConfig(route: Route): Route[] {
if (route.children) { if (route.children) {
return route.children; return route.children;

View File

@ -649,7 +649,6 @@ class ActivateRoutes {
const currRoot = this.currState ? this.currState._root : null; const currRoot = this.currState ? this.currState._root : null;
advanceActivatedRoute(this.futureState.root); advanceActivatedRoute(this.futureState.root);
this.activateChildRoutes(futureRoot, currRoot, parentOutletMap); this.activateChildRoutes(futureRoot, currRoot, parentOutletMap);
pushQueryParamsAndFragment(this.futureState);
} }
private activateChildRoutes( private activateChildRoutes(
@ -758,16 +757,6 @@ function closestLoadedConfig(
return b.length > 0 ? (<any>b[b.length - 1])._routeConfig._loadedConfig : null; return b.length > 0 ? (<any>b[b.length - 1])._routeConfig._loadedConfig : null;
} }
function pushQueryParamsAndFragment(state: RouterState): void {
if (!shallowEqual(state.snapshot.queryParams, (<any>state.queryParams).value)) {
(<any>state.queryParams).next(state.snapshot.queryParams);
}
if (state.snapshot.fragment !== (<any>state.fragment).value) {
(<any>state.fragment).next(state.snapshot.fragment);
}
}
function nodeChildrenAsMap(node: TreeNode<any>) { function nodeChildrenAsMap(node: TreeNode<any>) {
return node ? node.children.reduce((m: any, c: TreeNode<any>) => { return node ? node.children.reduce((m: any, c: TreeNode<any>) => {
m[c.value.outlet] = c; m[c.value.outlet] = c;

View File

@ -38,13 +38,21 @@ export class RouterState extends Tree<ActivatedRoute> {
/** /**
* @internal * @internal
*/ */
constructor( constructor(root: TreeNode<ActivatedRoute>, public snapshot: RouterStateSnapshot) {
root: TreeNode<ActivatedRoute>, public queryParams: Observable<Params>,
public fragment: Observable<string>, public snapshot: RouterStateSnapshot) {
super(root); super(root);
setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root); setRouterStateSnapshot<RouterState, ActivatedRoute>(this, root);
} }
/**
* @deprecated (Use root.queryParams)
*/
get queryParams(): Observable<Params> { return this.root.queryParams; }
/**
* @deprecated (Use root.fragment)
*/
get fragment(): Observable<string> { return this.root.fragment; }
toString(): string { return this.snapshot.toString(); } toString(): string { return this.snapshot.toString(); }
} }
@ -56,10 +64,10 @@ export function createEmptyState(urlTree: UrlTree, rootComponent: Type): RouterS
const emptyQueryParams = new BehaviorSubject({}); const emptyQueryParams = new BehaviorSubject({});
const fragment = new BehaviorSubject(''); const fragment = new BehaviorSubject('');
const activated = new ActivatedRoute( const activated = new ActivatedRoute(
emptyUrl, emptyParams, emptyData, PRIMARY_OUTLET, rootComponent, snapshot.root); emptyUrl, emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent,
snapshot.root);
activated.snapshot = snapshot.root; activated.snapshot = snapshot.root;
return new RouterState( return new RouterState(new TreeNode<ActivatedRoute>(activated, []), snapshot);
new TreeNode<ActivatedRoute>(activated, []), emptyQueryParams, fragment, snapshot);
} }
function createEmptyStateSnapshot(urlTree: UrlTree, rootComponent: Type): RouterStateSnapshot { function createEmptyStateSnapshot(urlTree: UrlTree, rootComponent: Type): RouterStateSnapshot {
@ -68,10 +76,9 @@ function createEmptyStateSnapshot(urlTree: UrlTree, rootComponent: Type): Router
const emptyQueryParams = {}; const emptyQueryParams = {};
const fragment = ''; const fragment = '';
const activated = new ActivatedRouteSnapshot( const activated = new ActivatedRouteSnapshot(
[], emptyParams, emptyData, PRIMARY_OUTLET, rootComponent, null, urlTree.root, -1, [], emptyParams, emptyQueryParams, fragment, emptyData, PRIMARY_OUTLET, rootComponent, null,
InheritedResolve.empty); urlTree.root, -1, InheritedResolve.empty);
return new RouterStateSnapshot( return new RouterStateSnapshot('', new TreeNode<ActivatedRouteSnapshot>(activated, []));
'', new TreeNode<ActivatedRouteSnapshot>(activated, []), emptyQueryParams, fragment);
} }
/** /**
@ -104,6 +111,7 @@ export class ActivatedRoute {
*/ */
constructor( constructor(
public url: Observable<UrlSegment[]>, public params: Observable<Params>, public url: Observable<UrlSegment[]>, public params: Observable<Params>,
public queryParams: Observable<Params>, public fragment: Observable<string>,
public data: Observable<Data>, public outlet: string, public component: Type|string, public data: Observable<Data>, public outlet: string, public component: Type|string,
futureSnapshot: ActivatedRouteSnapshot) { futureSnapshot: ActivatedRouteSnapshot) {
this._futureSnapshot = futureSnapshot; this._futureSnapshot = futureSnapshot;
@ -187,7 +195,8 @@ export class ActivatedRouteSnapshot {
* @internal * @internal
*/ */
constructor( constructor(
public url: UrlSegment[], public params: Params, public data: Data, public outlet: string, public url: UrlSegment[], public params: Params, public queryParams: Params,
public fragment: string, public data: Data, public outlet: string,
public component: Type|string, routeConfig: Route, urlSegment: UrlSegmentGroup, public component: Type|string, routeConfig: Route, urlSegment: UrlSegmentGroup,
lastPathIndex: number, resolve: InheritedResolve) { lastPathIndex: number, resolve: InheritedResolve) {
this._routeConfig = routeConfig; this._routeConfig = routeConfig;
@ -232,13 +241,21 @@ export class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {
/** /**
* @internal * @internal
*/ */
constructor( constructor(public url: string, root: TreeNode<ActivatedRouteSnapshot>) {
public url: string, root: TreeNode<ActivatedRouteSnapshot>, public queryParams: Params,
public fragment: string) {
super(root); super(root);
setRouterStateSnapshot<RouterStateSnapshot, ActivatedRouteSnapshot>(this, root); setRouterStateSnapshot<RouterStateSnapshot, ActivatedRouteSnapshot>(this, root);
} }
/**
* @deprecated (Use root.queryParams)
*/
get queryParams(): Params { return this.root.queryParams; }
/**
* @deprecated (Use root.fragment)
*/
get fragment(): string { return this.root.fragment; }
toString(): string { return serializeNode(this._root); } toString(): string { return serializeNode(this._root); }
} }
@ -259,6 +276,12 @@ function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string {
*/ */
export function advanceActivatedRoute(route: ActivatedRoute): void { export function advanceActivatedRoute(route: ActivatedRoute): void {
if (route.snapshot) { if (route.snapshot) {
if (!shallowEqual(route.snapshot.queryParams, route._futureSnapshot.queryParams)) {
(<any>route.queryParams).next(route._futureSnapshot.queryParams);
}
if (route.snapshot.fragment !== route._futureSnapshot.fragment) {
(<any>route.fragment).next(route._futureSnapshot.fragment);
}
if (!shallowEqual(route.snapshot.params, route._futureSnapshot.params)) { if (!shallowEqual(route.snapshot.params, route._futureSnapshot.params)) {
(<any>route.params).next(route._futureSnapshot.params); (<any>route.params).next(route._futureSnapshot.params);
(<any>route.data).next(route._futureSnapshot.data); (<any>route.data).next(route._futureSnapshot.data);
@ -269,6 +292,8 @@ export function advanceActivatedRoute(route: ActivatedRoute): void {
route.snapshot = route._futureSnapshot; route.snapshot = route._futureSnapshot;
} else { } else {
route.snapshot = route._futureSnapshot; route.snapshot = route._futureSnapshot;
// this is for resolved data
(<any>route.data).next(route._futureSnapshot.data); (<any>route.data).next(route._futureSnapshot.data);
} }
} }

View File

@ -200,10 +200,11 @@ describe('createUrlTree', () => {
function createRoot(tree: UrlTree, commands: any[], queryParams?: Params, fragment?: string) { function createRoot(tree: UrlTree, commands: any[], queryParams?: Params, fragment?: string) {
const s = new ActivatedRouteSnapshot( const s = new ActivatedRouteSnapshot(
[], <any>{}, <any>{}, PRIMARY_OUTLET, 'someComponent', null, tree.root, -1, <any>null); [], <any>{}, <any>{}, '', <any>{}, PRIMARY_OUTLET, 'someComponent', null, tree.root, -1,
<any>null);
const a = new ActivatedRoute( const a = new ActivatedRoute(
new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null),
PRIMARY_OUTLET, 'someComponent', s); new BehaviorSubject(null), new BehaviorSubject(null), PRIMARY_OUTLET, 'someComponent', s);
advanceActivatedRoute(a); advanceActivatedRoute(a);
return createUrlTree(a, tree, commands, queryParams, fragment); return createUrlTree(a, tree, commands, queryParams, fragment);
} }
@ -215,11 +216,11 @@ function create(
expect(segment).toBeDefined(); expect(segment).toBeDefined();
} }
const s = new ActivatedRouteSnapshot( const s = new ActivatedRouteSnapshot(
[], <any>{}, <any>{}, PRIMARY_OUTLET, 'someComponent', null, <any>segment, startIndex, [], <any>{}, <any>{}, '', <any>{}, PRIMARY_OUTLET, 'someComponent', null, <any>segment,
<any>null); startIndex, <any>null);
const a = new ActivatedRoute( const a = new ActivatedRoute(
new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null), new BehaviorSubject(null),
PRIMARY_OUTLET, 'someComponent', s); new BehaviorSubject(null), new BehaviorSubject(null), PRIMARY_OUTLET, 'someComponent', s);
advanceActivatedRoute(a); advanceActivatedRoute(a);
return createUrlTree(a, tree, commands, queryParams, fragment); return createUrlTree(a, tree, commands, queryParams, fragment);
} }

View File

@ -28,7 +28,7 @@ describe('Integration', () => {
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp, BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp, RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf, QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf,
ComponentRecordingQueryParams, ComponentRecordingRoutePathAndUrl, RouteCmp ComponentRecordingRoutePathAndUrl, RouteCmp
] ]
}); });
}); });
@ -289,29 +289,6 @@ describe('Integration', () => {
expect(fixture.debugElement.nativeElement).toHaveText('query: 2 fragment: fragment2'); expect(fixture.debugElement.nativeElement).toHaveText('query: 2 fragment: fragment2');
}))); })));
it('should not push query params into components that will be deactivated',
fakeAsync(
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
router.resetConfig([
{path: '', component: ComponentRecordingQueryParams},
{path: 'simple', component: SimpleCmp}
]);
const fixture = createRoot(tcb, router, RootCmp);
router.navigateByUrl('/?a=v1');
advance(fixture);
const c = fixture.debugElement.children[1].componentInstance;
expect(c.recordedQueryParams).toEqual([{}, {a: 'v1'}]);
router.navigateByUrl('/simple?a=v2');
advance(fixture);
expect(c.recordedQueryParams).toEqual([{}, {a: 'v1'}]);
})));
it('should push params only when they change', it('should push params only when they change',
fakeAsync( fakeAsync(
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => { inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
@ -1733,18 +1710,6 @@ class DummyLinkWithParentCmp {
constructor(route: ActivatedRoute) { this.exact = (<any>route.snapshot.params).exact === 'true'; } constructor(route: ActivatedRoute) { this.exact = (<any>route.snapshot.params).exact === 'true'; }
} }
@Component({template: ''})
class ComponentRecordingQueryParams {
recordedQueryParams: any[] = [];
subscription: any;
constructor(r: Router) {
this.subscription = r.routerState.queryParams.subscribe(r => this.recordedQueryParams.push(r));
}
ngOnDestroy() { this.subscription.unsubscribe(); }
}
@Component({selector: 'cmp', template: ''}) @Component({selector: 'cmp', template: ''})
class ComponentRecordingRoutePathAndUrl { class ComponentRecordingRoutePathAndUrl {
private path: any; private path: any;
@ -1764,7 +1729,7 @@ class ComponentRecordingRoutePathAndUrl {
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp, BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp, RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf, QueryParamsAndFragmentCmp, StringLinkButtonCmp, WrapperCmp, LinkInNgIf,
ComponentRecordingQueryParams, ComponentRecordingRoutePathAndUrl ComponentRecordingRoutePathAndUrl
] ]
}) })
class RootCmp { class RootCmp {

View File

@ -23,7 +23,7 @@ describe('RouterState & Snapshot', () => {
const root = new TreeNode(a, [new TreeNode(b, []), new TreeNode(c, [])]); const root = new TreeNode(a, [new TreeNode(b, []), new TreeNode(c, [])]);
state = new RouterStateSnapshot('url', root, {}, ''); state = new RouterStateSnapshot('url', root);
}); });
it('should return first child', () => { expect(state.root.firstChild).toBe(b); }); it('should return first child', () => { expect(state.root.firstChild).toBe(b); });
@ -60,7 +60,7 @@ describe('RouterState & Snapshot', () => {
const root = new TreeNode(a, [new TreeNode(b, []), new TreeNode(c, [])]); const root = new TreeNode(a, [new TreeNode(b, []), new TreeNode(c, [])]);
state = new RouterState(root, <any>null, <any>null, <any>null); state = new RouterState(root, <any>null);
}); });
it('should return first child', () => { expect(state.root.firstChild).toBe(b); }); it('should return first child', () => { expect(state.root.firstChild).toBe(b); });
@ -87,9 +87,11 @@ describe('RouterState & Snapshot', () => {
function createActivatedRouteSnapshot(cmp: string) { function createActivatedRouteSnapshot(cmp: string) {
return new ActivatedRouteSnapshot( return new ActivatedRouteSnapshot(
<any>null, <any>null, <any>null, <any>null, <any>cmp, <any>null, <any>null, -1, null); <any>null, <any>null, <any>null, <any>null, <any>null, <any>null, <any>cmp, <any>null,
<any>null, -1, null);
} }
function createActivatedRoute(cmp: string) { function createActivatedRoute(cmp: string) {
return new ActivatedRoute(<any>null, <any>null, <any>null, <any>null, <any>cmp, <any>null); return new ActivatedRoute(
<any>null, <any>null, <any>null, <any>null, <any>null, <any>null, <any>cmp, <any>null);
} }

View File

@ -1,9 +1,15 @@
/** @stable */ /** @stable */
export declare class ActivatedRoute { export declare class ActivatedRoute {
children: ActivatedRoute[];
component: Type | string; component: Type | string;
data: Observable<Data>; data: Observable<Data>;
firstChild: ActivatedRoute;
fragment: Observable<string>;
outlet: string; outlet: string;
params: Observable<Params>; params: Observable<Params>;
parent: ActivatedRoute;
pathFromRoot: ActivatedRoute[];
queryParams: Observable<Params>;
routeConfig: Route; routeConfig: Route;
snapshot: ActivatedRouteSnapshot; snapshot: ActivatedRouteSnapshot;
url: Observable<UrlSegment[]>; url: Observable<UrlSegment[]>;
@ -12,10 +18,16 @@ export declare class ActivatedRoute {
/** @stable */ /** @stable */
export declare class ActivatedRouteSnapshot { export declare class ActivatedRouteSnapshot {
children: ActivatedRouteSnapshot[];
component: Type | string; component: Type | string;
data: Data; data: Data;
firstChild: ActivatedRouteSnapshot;
fragment: string;
outlet: string; outlet: string;
params: Params; params: Params;
parent: ActivatedRouteSnapshot;
pathFromRoot: ActivatedRouteSnapshot[];
queryParams: Params;
routeConfig: Route; routeConfig: Route;
url: UrlSegment[]; url: UrlSegment[];
toString(): string; toString(): string;
@ -249,16 +261,16 @@ export declare class RouterOutletMap {
/** @stable */ /** @stable */
export declare class RouterState extends Tree<ActivatedRoute> { export declare class RouterState extends Tree<ActivatedRoute> {
fragment: Observable<string>; /** @deprecated */ fragment: Observable<string>;
queryParams: Observable<Params>; /** @deprecated */ queryParams: Observable<Params>;
snapshot: RouterStateSnapshot; snapshot: RouterStateSnapshot;
toString(): string; toString(): string;
} }
/** @stable */ /** @stable */
export declare class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> { export declare class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {
fragment: string; /** @deprecated */ fragment: string;
queryParams: Params; /** @deprecated */ queryParams: Params;
url: string; url: string;
toString(): string; toString(): string;
} }