Skip to content

Fixes for Typed Params #1443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 21, 2014
14 changes: 14 additions & 0 deletions src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ function objectKeys(object) {
return result;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protoKeys was necessary because I used prototypal inheritance for ParamsSet, like we discussed. objectKeys doesn't work for prototypal inheritance.

/**
* like objectKeys, but includes keys from prototype chain.
* @param object the object whose prototypal keys will be returned
* @param ignoreKeys an array of keys to ignore
*/
function protoKeys(object, ignoreKeys) {
var result = [];
for (var key in object) {
if (!ignoreKeys || ignoreKeys.indexOf(key) === -1)
result.push(key);
}
return result;
}

/**
* IE8-safe wrapper for `Array.prototype.indexOf()`.
*
Expand Down
73 changes: 34 additions & 39 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {

var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
var root, states = {}, $state, queue = {}, abstractKey = 'abstract', isRuntime = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toggle isRuntime when $get is called.


// Builds state properties from definition passed to registerState()
var stateBuilder = {
Expand Down Expand Up @@ -64,12 +64,19 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
return state.url ? state : (state.parent ? state.parent.navigable : null);
},

// Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ownParams is an instanceof ParamSet. It combines url: params with params defined in state.params.

ownParams: function(state) {
var params = state.url && state.url.params || new $$UMFP.ParamSet();
forEach(state.params || {}, function(config, id) {
if (!params[id]) params[id] = new $$UMFP.Param(id, null, config);
});
return params;
},

// Derive parameters for this state and ensure they're a super-set of parent's parameters
params: function(state) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

params is an instance of ParamsSet. It is ownParams prototypally inherited from parent.params

if (!state.params) {
return state.url ? state.url.params : state.parent.params;
}
return state.params;
var parentParams = state.parent && state.parent.params || new $$UMFP.ParamSet();
return inherit(parentParams, state.ownParams);
},

// If there is no explicit multi-view configuration, make one up so we don't have
Expand All @@ -87,28 +94,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
return views;
},

ownParams: function(state) {
state.params = state.params || {};

if (!state.parent) {
return objectKeys(state.params);
}
var paramNames = {}; forEach(state.params, function (v, k) { paramNames[k] = true; });

forEach(state.parent.params, function (v, k) {
if (!paramNames[k]) {
throw new Error("Missing required parameter '" + k + "' in state '" + state.name + "'");
}
paramNames[k] = false;
});
var ownParams = [];

forEach(paramNames, function (own, p) {
if (own) ownParams.push(p);
});
return ownParams;
},

// Keep a full path from the root down to this state as this is needed for state activation.
path: function(state) {
return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
Expand Down Expand Up @@ -171,6 +156,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
queue[parentName].push(state);
}

function flushQueuedChildren(parentName) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this fn extracted from $state.registerState()

var queued = queue[parentName] || [];
while(queued.length) {
registerState(queued.shift());
}
}

function registerState(state) {
// Wrap a new object around the state so we can store our private details easily.
state = inherit(state, {
Expand All @@ -186,10 +178,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
// Get parent name
var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
: (isString(state.parent)) ? state.parent
: (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow parentName to load from parent object reference

: '';

// If parent is not registered yet, add state to queue and register later
if (parentName && !states[parentName]) {
if (name !== "" && (!isRuntime || !states[parentName])) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only register the root state during config-time. otherwise queue the state def.

return queueState(parentName, state.self);
}

Expand All @@ -208,11 +201,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
}

// Register any queued children
if (queue[name]) {
for (var i = 0; i < queue[name].length; i++) {
registerState(queue[name][i]);
}
}
flushQueuedChildren(name);

return state;
}
Expand Down Expand Up @@ -538,8 +527,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
* you're coming from.
*/
this.$get = $get;
$get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter'];
function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter) {
$get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {

var TransitionSuperseded = $q.reject(new Error('transition superseded'));
var TransitionPrevented = $q.reject(new Error('transition prevented'));
Expand Down Expand Up @@ -793,6 +782,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
}
if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
if (!toState.params.$$validates(toParams)) return TransitionFailed;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate $state.transitionTo() params against their types.


toParams = toState.params.$$values(toParams);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert url params to their typed values (also converts/normalizes $state.transitionTo() params via the Type).

to = toState;

var toPath = to.path;
Expand All @@ -801,7 +793,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];

if (!options.reload) {
while (state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams)) {
while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
locals = toLocals[keep] = state.locals;
keep++;
state = toPath[keep];
Expand All @@ -820,7 +812,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
}

// Filter parameters before we pass them to event handlers etc.
toParams = filterByKeys(objectKeys(to.params), toParams || {});
toParams = filterByKeys(to.params.$$keys(), toParams || {});

// Broadcast start event and cancel the transition if requested
if (options.notify) {
Expand Down Expand Up @@ -1133,7 +1125,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
if (!nav || nav.url === undefined || nav.url === null) {
return null;
}
return $urlRouter.href(nav.url, filterByKeys(objectKeys(state.params), params || {}), {
return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys(), params || {}), {
absolute: options.absolute
});
};
Expand Down Expand Up @@ -1162,7 +1154,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
// necessary. In addition to being available to the controller and onEnter/onExit callbacks,
// we also need $stateParams to be available for any $injector calls we make during the
// dependency resolution process.
var $stateParams = (paramsAreFiltered) ? params : filterByKeys(objectKeys(state.params), params);
var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
var locals = { $stateParams: $stateParams };

// Resolve 'global' dependencies for the state, i.e. those not specific to a view.
Expand Down Expand Up @@ -1203,6 +1195,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
});
}

isRuntime = true;
flushQueuedChildren("");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once $get() has been called, begin flushing queued states starting from "" (root)


return $state;
}

Expand Down
Loading