Skip to content

[3.6+] Query Params behavior changes with RouterService when refreshModel is true #18683

Open
@chriskrycho

Description

@chriskrycho

While upgrading our application from Ember 3.4 to 3.8, we discovered that the behavior of query params in the URL changes dramatically depending on whether any of the query params have refreshModel: true set on the route. I created a sample example reproduction in this repo.

When query params do not have refreshModel: true set, using RouterService#transitionTo ultimately results in query params being removed from the URL once the target route resolves. However, if refreshModel: true is set for any of the query params, the query params remain in the URL once the target route resolves. While in principle this should always still work in a well-behaved application, it is definitely not desirable behavior (because not all applications are well behaved here!).

The issue is surfaced because the presence of refreshModel leads Route#queryParamsDidChange to invoke Route#refresh:

queryParamsDidChange(this: Route, changed: {}, _totalPresent: unknown, removed: {}) {
let qpMap = get(this, '_qp').map;
let totalChanged = Object.keys(changed).concat(Object.keys(removed));
for (let i = 0; i < totalChanged.length; ++i) {
let qp = qpMap[totalChanged[i]];
if (
qp &&
get(this._optionsForQueryParam(qp), 'refreshModel') &&
this._router.currentState
) {
this.refresh();
break;
}
}
return true;
},

However, this is not the actual cause, just the surfacing symptom. The actual cause is that queryParamsDidChange is itself invoked with incorrect parameters. (It surfaces the issue clearly because invoking Route#refresh creates a new Transition, which carries along those query params.)

The call stack is:

  • queryParamsDidChange (router.js)
  • triggerEvent with event named "queryParamsDidChange" (router.js)
  • triggerEvent with event named "queryParamsDidChange" (router.js) – this is not a mistake, there are two layers of event triggering
  • fireQueryParamsDidChange (router_js.js)
  • queryParamsTransition (router_js.js)
  • getTransitionByIntent (router_js.js)

The getTransitionByIntent method calculates a queryParamChangelist which is then supplied to the rest of the chain, but has insufficient information to correctly generate that queryParamChangelist: it only knows the actual previous state of the query params for the route and the new query params… but (correctly) has no knowledge of Ember's Controller query params special handling.

This does not fail in the non-router-service case because when the transition comes from a route or a controller, _prepareQueryParams (again, correctly, per the RFC) prunes default query params from the list of query params—it only skips that operation when the transition comes from the router service:

if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.routeInfos, queryParams);
}

Thus, the list of query params passed on down the chain still includes all values, whether or not they are defaults… so what ends up being checked by the the router microlib for different values includes those values.

At first blush, none of the changes we could make in this space are obvious winners, or even particularly good. 😬

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions