Skip to content
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

Double transitions - analysis and fix #1573

Closed
christopherthielen opened this issue Nov 20, 2014 · 0 comments
Closed

Double transitions - analysis and fix #1573

christopherthielen opened this issue Nov 20, 2014 · 0 comments

Comments

@christopherthielen
Copy link
Contributor

We've had troubles with double-transitions in many different corner cases. The root cause of this is a mismatch between the $stateParams, and the params decoded from the URL. The solution is to ensure that all values that can map to a url, also map from the url exactly.

Old example (already fixed).

Given the state .state('foo', { url: "/foo/:bar" }) and the transition $state.go("foo", { bar: undefined })

  • transitionTo('foo', { bar: undefined })
    • $state.params is set to { bar: undefined }
    • $state.$current.url.format($state.params)) yields /foo/
    • the url is set via $urlRouter.push("/foo/");
  • $urlRouter.listen handles the $locationChangeSuccess event
    • The url has changed (from "" to "/foo/")
    • Find the correct matcher (it finds foo state's matcher)
    • The UrlMatcher parses "/foo/" and matches the empty string. It returns { bar: "" }
    • Call transitionTo with the matched details
  • $state.transitionTo("foo", { bar: "" });
  • Check if toParams match fromParams
  • {bar: undefined} does not equal { bar: "" }, so we have "new params"
  • continue transition to foo with params {bar: ""} <--- Double transition

New example

Given the state .state('foo', { url: "/foo", params: { bar: null } }) and the transition $state.go("foo", { bar: { blah: 45 } })

  • transitionTo('foo', { bar: { blah: 45 } })
    • $state.params is set to { bar: { blah: 45 } }
    • $state.$current.url.format($state.params)) yields /foo
    • the url is set via $urlRouter.push("/foo");
  • $urlRouter.listen handles the $locationChangeSuccess event
    • The url has changed (from "" to "/foo")
    • Find the correct matcher (it finds foo state's matcher)
    • The UrlMatcher parses "/foo" and returns any matched params. It returns { } because there are no params in the url string.
    • Call transitionTo with the matched details
  • $state.transitionTo("foo", { });
  • Check if toParams match fromParams
  • {bar: { blah: 45 } } does not equal { }, so we have "new params"
  • continue transition to foo with params { } <--- Double transition

Approach

Part 1
  • Ensure parameters map cleanly between a type and a url string
    • The Type system provides the mechanism.
    • Values in $stateParams are always typed
      • transitionTo gets decodes the incoming toParams before setting $stateParams
  • Ensure all parameters map cleanly between empty string, null, and undefined
    • Preprocess parameters sent to the transitionTo method and apply explicit mappings
    • Param.$value applies the replacement mapping before decoding the value using the Type
    • getReplace function:
    function getReplace(config, arrayMode, isOptional, squash) {
      var replace, configuredKeys, defaultPolicy = [
        { from: "",   to: (isOptional || arrayMode ? undefined : "") },
        { from: null, to: (isOptional || arrayMode ? undefined : "") }
      ];
      replace = isArray(config.replace) ? config.replace : [];
      if (isString(squash))
        replace.push({ from: squash, to: undefined });
      configuredKeys = map(replace, function(item) { return item.from; } );
      return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
    }
Part 2

Transitions to states with non-url parameters should be handled correctly too. This is problematic when we re-synchronize the URL, because non-url parameters are obviously not addressed by parsing the URL.

  • Set inherit: true when performing URL matching
    • When transitioning to the same state via re-sync url, or via a url match to a child state, the non-url parameters are inherited correctly.
    • Potential for unintended inherits?
  • After pushing the URL to $urlRouter following a successful transition, do not attempt to resynchronize in response to a $locationChangeSuccess event.
    • The transition was the cause of the location change, so re-synchronizing via the URL should be unnecessary.
    • This is a safety mechanism only. The approach in part 1 should allow url-resync to exactly match the current state anyway.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant