From 10b7fdece3e7b36766a65cbd5f2aeef341921379 Mon Sep 17 00:00:00 2001 From: Chris Thielen Date: Mon, 28 May 2018 20:50:43 -0700 Subject: [PATCH] feat(transition): Added transition.paramsChanged() to get added/deleted/changed parameter values for a transition --- src/transition/transition.ts | 82 ++++++++++++++++++++++++++++++++++++ test/transitionSpec.ts | 68 ++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/src/transition/transition.ts b/src/transition/transition.ts index 5b57a24c..b05392f3 100644 --- a/src/transition/transition.ts +++ b/src/transition/transition.ts @@ -37,6 +37,7 @@ import { UIInjector } from '../interface'; import { RawParams } from '../params/interface'; import { ResolvableLiteral } from '../resolve/interface'; import { Rejection } from './rejectFactory'; +import { applyPairs, flattenR, uniqR } from '../common'; /** @hidden */ const stateSelf: (_state: StateObject) => StateDeclaration = prop('self'); @@ -286,6 +287,87 @@ export class Transition implements IHookRegistry { return Object.freeze(this._treeChanges[pathname].map(prop('paramValues')).reduce(mergeR, {})); } + /** + * Gets the new values of any parameters that changed during this transition. + * + * Returns any parameter values that have changed during a transition, as key/value pairs. + * + * - Any parameter values that have changed will be present on the returned object reflecting the new value. + * - Any parameters that *not* have changed will not be present on the returned object. + * - Any new parameters that weren't present in the "from" state, but are now present in the "to" state will be present on the returned object. + * - Any previous parameters that are no longer present (because the "to" state doesn't have them) will be included with a value of `undefined`. + * + * The returned object is immutable. + * + * #### Examples: + * + * Given: + * ```js + * var stateA = { name: 'stateA', url: '/stateA/:param1/param2' } + * var stateB = { name: 'stateB', url: '/stateB/:param3' } + * var stateC = { name: 'stateB.nest', url: '/nest/:param4' } + * ``` + * + * #### Example 1 + * + * From `/stateA/abc/def` to `/stateA/abc/xyz` + * + * ```js + * var changed = transition.paramsChanged() + * // changed is { param2: 'xyz' } + * ``` + * + * The value of `param2` changed to `xyz`. + * The value of `param1` stayed the same so its value is not present. + * + * #### Example 2 + * + * From `/stateA/abc/def` to `/stateB/123` + * + * ```js + * var changed = transition.paramsChanged() + * // changed is { param1: undefined, param2: undefined, param3: '123' } + * ``` + * + * The value `param3` is present because it is a new param. + * Both `param1` and `param2` are no longer present so their value is undefined. + * + * #### Example 3 + * + * From `/stateB/123` to `/stateB/123/nest/456` + * + * ```js + * var changed = transition.paramsChanged() + * // changed is { param4: '456' } + * ``` + * + * The value `param4` is present because it is a new param. + * The value of `param3` did not change, so its value is not present. + * + * @returns an immutable object with changed parameter keys/values. + */ + paramsChanged(): { [paramName: string]: any }; + paramsChanged(): T; + paramsChanged() { + const fromParams = this.params('from'); + const toParams = this.params('to'); + + // All the parameters declared on both the "to" and "from" paths + const allParamDescriptors: Param[] = [] + .concat(this._treeChanges.to) + .concat(this._treeChanges.from) + .map(pathNode => pathNode.paramSchema) + .reduce(flattenR, []) + .reduce(uniqR, []); + + const changedParamDescriptors = Param.changed(allParamDescriptors, fromParams, toParams); + + return changedParamDescriptors.reduce((changedValues, descriptor) => { + changedValues[descriptor.id] = toParams[descriptor.id]; + return changedValues; + }, {}); + } + /** * Creates a [[UIInjector]] Dependency Injector * diff --git a/test/transitionSpec.ts b/test/transitionSpec.ts index 85bce38a..1156556f 100644 --- a/test/transitionSpec.ts +++ b/test/transitionSpec.ts @@ -1199,6 +1199,74 @@ describe('transition', function() { }); }); + describe('paramsChanged()', () => { + beforeEach(() => { + router.stateRegistry.register({ name: 'stateA', url: '/stateA/:param1/:param2' }); + router.stateRegistry.register({ name: 'stateB', url: '/stateB/:param3' }); + router.stateRegistry.register({ name: 'stateB.nest', url: '/nest/:param4' }); + }); + + it('should contain only changed param values', async done => { + await $state.go('stateA', { param1: 'abc', param2: 'def' }); + const goPromise = $state.go('stateA', { param1: 'abc', param2: 'xyz' }); + await goPromise; + + const changed = goPromise.transition.paramsChanged(); + expect(Object.keys(changed)).toEqual(['param2']); + expect(changed).toEqual({ param2: 'xyz' }); + + done(); + }); + + it('should contain new params values when switching states', async done => { + await $state.go('stateB', { param3: '123' }); + const goPromise = $state.go('stateB.nest', { param3: '123', param4: '456' }); + await goPromise; + + const changed = goPromise.transition.paramsChanged(); + expect(Object.keys(changed)).toEqual(['param4']); + expect(changed).toEqual({ param4: '456' }); + + done(); + }); + + it('should contain removed params with `undefined` when switching states', async done => { + await $state.go('stateB.nest', { param3: '123', param4: '456' }); + const goPromise = $state.go('stateB'); + await goPromise; + + const changed = goPromise.transition.paramsChanged(); + expect(Object.keys(changed)).toEqual(['param4']); + expect(changed).toEqual({ param4: undefined }); + + done(); + }); + + it('should contain removed params with `undefined` when switching states', async done => { + await $state.go('stateB.nest', { param3: '123', param4: '456' }); + const goPromise = $state.go('stateB'); + await goPromise; + + const changed = goPromise.transition.paramsChanged(); + expect(Object.keys(changed)).toEqual(['param4']); + expect(changed).toEqual({ param4: undefined }); + + done(); + }); + + it('should contain added and removed params', async done => { + await $state.go('stateB.nest', { param3: '123', param4: '456' }); + const goPromise = $state.go('stateA', { param1: 'abc', param2: 'def' }); + await goPromise; + + const changed = goPromise.transition.paramsChanged(); + expect(Object.keys(changed).sort()).toEqual(['param1', 'param2', 'param3', 'param4']); + expect(changed).toEqual({ param1: 'abc', param2: 'def', param3: undefined, param4: undefined }); + + done(); + }); + }); + describe('from previous transitions', () => { it('should get their Transition resolves cleaned up', async done => { router.stateRegistry.register({ name: 'resolve', resolve: { foo: () => 'Some data' } });