From beca1f5fd14fb2d7280dd1a6644fd26aa216f185 Mon Sep 17 00:00:00 2001
From: Chris Thielen <github@sandgnat.com>
Date: Tue, 29 Nov 2016 19:38:53 -0600
Subject: [PATCH] fix(StateQueueManager): Compare parsed url parameters using
 typed parameters - Removed `equalForKeys`

feat(State): add .parameters() option for filtering to matching keys
---
 src/common/common.ts           | 17 -----------------
 src/state/stateObject.ts       |  8 +++++---
 src/state/stateQueueManager.ts | 10 ++++++++--
 src/state/stateService.ts      |  5 ++---
 4 files changed, 15 insertions(+), 25 deletions(-)

diff --git a/src/common/common.ts b/src/common/common.ts
index 1357c7c7..5fac6621 100644
--- a/src/common/common.ts
+++ b/src/common/common.ts
@@ -179,23 +179,6 @@ export function ancestors(first: State, second: State) {
   return path;
 }
 
-/**
- * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
- *
- * @param {Object} a The first object.
- * @param {Object} b The second object.
- * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
- *                     it defaults to the list of keys in `a`.
- * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
- */
-export function equalForKeys(a: Obj, b: Obj, keys: string[] = Object.keys(a)) {
-  for (var i = 0; i < keys.length; i++) {
-    let k = keys[i];
-    if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
-  }
-  return true;
-}
-
 type PickOmitPredicate = (keys: string[], key: string) => boolean;
 function pickOmitImpl(predicate: PickOmitPredicate, obj: Obj, ...keys: string[]) {
   let objCopy = {};
diff --git a/src/state/stateObject.ts b/src/state/stateObject.ts
index 7ff5cc9c..9b70d4df 100644
--- a/src/state/stateObject.ts
+++ b/src/state/stateObject.ts
@@ -140,13 +140,15 @@ export class State {
    *
    * Gets [[Param]] information that is owned by the state.
    * If `opts.inherit` is true, it also includes the ancestor states' [[Param]] information.
+   * If `opts.matchingKeys` exists, returns only `Param`s whose `id` is a key on the `matchingKeys` object
    *
    * @param opts options
    */
-  parameters(opts?: { inherit?: boolean }): Param[] {
-    opts = defaults(opts, { inherit: true });
+  parameters(opts?: { inherit?: boolean, matchingKeys?: any }): Param[] {
+    opts = defaults(opts, { inherit: true, matchingKeys: null });
     let inherited = opts.inherit && this.parent && this.parent.parameters() || [];
-    return inherited.concat(values(this.params));
+    return inherited.concat(values(this.params))
+        .filter(param => !opts.matchingKeys || opts.matchingKeys.hasOwnProperty(param.id));
   }
 
   /**
diff --git a/src/state/stateQueueManager.ts b/src/state/stateQueueManager.ts
index 2c4b8615..cae49edb 100644
--- a/src/state/stateQueueManager.ts
+++ b/src/state/stateQueueManager.ts
@@ -1,5 +1,5 @@
 /** @module state */ /** for typedoc */
-import {extend, inherit, pluck, equalForKeys} from "../common/common";
+import {extend, inherit, pluck} from "../common/common";
 import {isString} from "../common/predicates";
 import {StateDeclaration} from "./interface";
 import {State} from "./stateObject";
@@ -8,6 +8,7 @@ import {StateService} from "./stateService";
 import {UrlRouterProvider} from "../url/urlRouter";
 import {RawParams} from "../params/interface";
 import {StateRegistry, StateRegistryListener} from "./stateRegistry";
+import { Param } from "../params/param";
 
 export class StateQueueManager {
   queue: State[];
@@ -105,7 +106,12 @@ export class StateQueueManager {
     if (state.abstract || !state.url) return;
 
     $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match: RawParams, $stateParams: RawParams) {
-      if ($state.$current.navigable !== state || !equalForKeys($match, $stateParams)) {
+      function matchedParamsEqual() {
+        let schema: Param[] = state.parameters({ inherit: true, matchingKeys: $match });
+        return Param.equals(schema, Param.values(schema, $match), $stateParams);
+      }
+
+      if ($state.$current.navigable !== state || !matchedParamsEqual()) {
         $state.transitionTo(state, $match, { inherit: true, source: "url" });
       }
     }], (rule) => state._urlRule = rule);
diff --git a/src/state/stateService.ts b/src/state/stateService.ts
index 2f840569..a8fa292b 100644
--- a/src/state/stateService.ts
+++ b/src/state/stateService.ts
@@ -20,7 +20,6 @@ import {RawParams} from "../params/interface";
 import {ParamsOrArray} from "../params/interface";
 import {Param} from "../params/param";
 import {Glob} from "../common/glob";
-import {equalForKeys} from "../common/common";
 import {HrefOptions} from "./interface";
 import {bindFunctions} from "../common/common";
 import {Globals} from "../globals";
@@ -412,7 +411,7 @@ export class StateService {
     if (this.$current !== state) return false;
     if (!params) return true;
 
-    let schema: Param[] = state.parameters({ inherit: true }).filter(param => params.hasOwnProperty(param.id));
+    let schema: Param[] = state.parameters({ inherit: true, matchingKeys: params });
     return Param.equals(schema, Param.values(schema, params), this.params);
   };
 
@@ -468,7 +467,7 @@ export class StateService {
     if (!isDefined(include[state.name])) return false;
     if (!params) return true;
 
-    let schema: Param[] = state.parameters({ inherit: true }).filter(param => params.hasOwnProperty(param.id));
+    let schema: Param[] = state.parameters({ inherit: true, matchingKeys: params });
     return Param.equals(schema, Param.values(schema, params), this.params);
   };