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

Add queryParams to the router service #380

Open
wants to merge 23 commits into
base: master
from

Conversation

@NullVoxPopuli
Copy link
Contributor

commented Sep 22, 2018

Rendered

NullVoxPopuli added some commits Sep 22, 2018

@nickschot

This comment has been minimized.

Copy link

commented Sep 22, 2018

I think the RFC is not entirely clear on that it also proposes to make QP's writeable through the service (it does, doesn't it?). If so this would mean that route specific query parameters (e.g. /my-route?my-route-specific-qp=true) would be kept when navigating to /my-other-route? In the current behaviour the QP is "removed" from the URL but restored when you go back to /my-route which is quite nice for routes with filters/search functionality. Would similar behaviour still be possible?

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 22, 2018

I think the RFC is not entirely clear on that it also proposes to make QP's writeable through the service (it does, doesn't it?)

it hints at it a bit, but maybe that should be out of scope for now. Based on the behavior you describe, setting a query param would instantly cause a transition -- obviously not ideal. Though, I wonder if in the Transition logging, we could say that a transition was initiated by a query param?

I also suggested that this replace the current implementation of query params eventually -- in order to achieve that auto-routing behavior, the logic for that would have to live in some service, rather than in the controller/route? The QP wouldn't be able to live on a route, cause routes don't exist eternally -- though, do controllers? is that why QPs are there currently? maybe the controller just sets up QP usage from the router service?

overall, having query params be global, may still simplify the overall understanding of how to work with them.

Update (2019-06-09)

writable query params have been added

@nickschot

This comment has been minimized.

Copy link

commented Sep 22, 2018

afaik controllers are Singleton and keep their values (and thus qp’s).

NullVoxPopuli added some commits Sep 23, 2018

@NullVoxPopuli NullVoxPopuli changed the title Draft: Query Params as a Computed Property on the RouterService Query Params as a Computed Property on the RouterService Sep 23, 2018

@lougreenwood

This comment has been minimized.

Copy link

commented Sep 24, 2018

@NullVoxPopuli Thanks for writing this up, this has been on my mind recently too!

I wonder, how do you envisage some of the use cases if QP's are read-only?

For example, if a QP is used to manage opening modals, presumably closing a modal should remove that QP, but if it's read-only, the modal component (or modal manager service or whatever) wouldn't be able to directly do that?

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 24, 2018

@lougreenwood maybe that depends on usage / defers the readonlyness to app-space?

like,

export default component SomeComponent exetends Component {
  @service router;

  @readOnly('router.queryParams.isModalOpen') isModalOpen;
}

Though, this could lead to app-devs trolling themselves by declaring it as non-read-only elsewhere in the app.

@Gaurav0

This comment has been minimized.

Copy link
Contributor

commented Sep 24, 2018

It should be pointed out that one can already access the controller for the current route as long as one has access to the router service and the owner.
currentController = this.owner.lookup(`controller:${this.currentRouteName}`)
Thus this computed property is simply a convenience and can be implemented now as an addon now to test out the concept.

It also suggests a simpler, alternative implementation of the design, to simply alias the properties on the current controller. This would not have a drawback of breaking any existing applications.

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 24, 2018

@Gaurav0 aliasing to that on the router service would be a dirt simple implementation.
And controllers could still be used for defaults and such.

What would the behavior be if a query param isn't found? (or it was on a different controller) just return undefined?

@lougreenwood

This comment has been minimized.

Copy link

commented Sep 24, 2018

@Gaurav0 Would this assume that the query param were already explicitly defined on the controller, or could this method capture any QP entered as part of the URL?

To me, the real power comes when QPs are something like some other state storage mechanism that a component can directly reference/manipulate.

For example, I might have a modal component which is used on many routes, the modal may use some QP to toggle it's visibility. Having to define every QP on every controller where the component may or may not be used becomes very cumbersome and IMO fragile.

@lougreenwood

This comment has been minimized.

Copy link

commented Sep 24, 2018

Maybe it's out of the scope of this discussion... but thinking more about what QPs provide, could QPs be abstractly described as just some kind of serialised state storage mechanism? Why is this a routing concern at all (seems the route/URL is just the persistence method), it strikes me as an application concern which should possibly exist as some service (similar to store) which is available to any consumer in the application.

By associating QPs with routes & controllers, are we "missing the forest for the trees"?

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 24, 2018

I super agree that QPs should be a state storage, rather than a route concern.

@Gaurav0

This comment has been minimized.

Copy link
Contributor

commented Sep 24, 2018

What would the behavior be if a query param isn't found? (or it was on a different controller) just return undefined?

This is a very good question. I do suggest it should be undefined, as just about anything else would make TypeScript not easily work. But why don't we go ahead and implement an addon testing out this concept and see if it makes sense practically in real applications?

@Gaurav0 Would this assume that the query param were already explicitly defined on the controller, or could this method capture any QP entered as part of the URL?

To me, the real power comes when QPs are something like some other state storage mechanism that a component can directly reference/manipulate.

For example, I might have a modal component which is used on many routes, the modal may use some QP to toggle it's visibility. Having to define every QP on every controller where the component may or may not be used becomes very cumbersome and IMO fragile.

You don't have to put the query param on every controller, you can just put it on the application controller today and reference that by injecting it where ever you need it. The current system is more flexible than it looks by just reading the guides.

@tylerturdenpants tylerturdenpants referenced this pull request Sep 25, 2018
6 of 8 tasks complete
@lupestro

This comment has been minimized.

Copy link
Contributor

commented Sep 27, 2018

The current mechanism has no means to ask for the "remainder" of parameters - whatever other parameters exist that haven't been identified explicitly as known properties. Will such a mechanism exist here?

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 27, 2018

Will such a mechanism exist here?

Yes, because the whole of the query params would be accessible from a singular computed property on the router service. :)

That computed property could then allow access to other computed properties representing individual parameters

@sandstrom

This comment has been minimized.

Copy link

commented Sep 27, 2018

It would be great to separate query params from controllers (thanks @NullVoxPopuli for starting this discussion 👏 ). However, I think this is more complicated than this RFC makes it sound like.

Instead of an RFC proposing a specific change, I think it would be better to start discussing the set of different options that are available, and what requirements [functionality] need to be supported in a future query params solution. That discussion could start as an issue in this repo (here is an example: #379).

It would also be good to loop in the resident query params expert @machty and hear his thoughts! Probably also someone else on the core team, since it's good to sync with other planned functionality.

Different options

  • Query params owned by the route
  • Query params owned by controller (currently the case)
  • Query params owned by router service
  • [Other ideas here…]

Various questions

  • How to handle stickiness? [a good, existing, feature]
  • Should it be possible to bind to a query param? (from components, from routes)
  • Should read/write go through the same methods, or maybe all writing (updating) should go through transitionTo? [I think the current functionality of setting QPs via controller has some issues]
  • How about serialization (of keys, values)? [a good, existing, feature]
  • The RFC mentions serialization/deserialization, but not clear how to set it up for all routes/query-params, i.e. in the proposed service.
  • How to handle default values? [currently hidden in url, but the query param key/value exist, which is great in my opinion]
  • When are query params set (lifecycle-wise)? (both when entering route via 'url' [page load] and via transition]
  • Related to the previous question, if there are bindings, when are they updated (before/after the model hook has resolved?, something else?)
  • Should some concerns be split? For example, the 'stickiness' of QPs could be handled at the route level by remembering the last value when the route is exited, stored in some storage service, and then restored when the route is accessed again. Should this be the responsibility of the user, or the framework? and even if it's part of the framework, how should that be handled?
  • How to forbid multiple active routes (ancestor1 -> ancestor2 -> leaf) from sharing the same QP name? (name collisions)
  • How to handle serialization from e.g. numbers to strings? (qp values) I.e. should qp's have a native "type" (string/number/etc)?
  • [More questions that I haven't thought of…]
@lougreenwood

This comment has been minimized.

Copy link

commented Sep 27, 2018

@sandstrom I've been generally thinking about this, and whilst my idea isn't fully developed, I'm beginning to wonder if QPs, from an application perspective, could be handled by some state service like ember-state-manager (https://github.com/offirgolan/ember-state-manager) - and QPs read/write are handled by some adapter that writes the state to the URL.

I haven't explored the idea in enough depth, but I wonder if that would then allow controllers/routes/components etc to bind to values in the state manager and the handling of the QP specific logic becomes a hidden abstraction from the actual use of QP values?

Something like:
Component <> State service with QP backing <> current URL (& optional route transition?)

@allthesignals

This comment has been minimized.

Copy link

commented Sep 27, 2018

I have personally found query params to be both incredibly important to the kind of apps we work on as well as perhaps a weaker part of the framework. This perception probably comes down to the fact that the apps I work on require an unusual number of query params, sometimes even "dynamically defined" (something currently not possible without serializing custom hashes into the URL). But it's also more concretely that they aren't fully integrated into things like computed properties (you cannot reliably use a CP as a query param, even if you define a custom setter). I believe "QPs-as-a-service" would help improve the usability of QPs by making them easier to inject into components that update based on their state. This improves component reusability by de-coupling them from their controller context and making them easier to configure across different application use cases.

On numerous occasions I have needed to pass many query parameters into components. This has led to threading dozens of properties through components when a service might have worked better. Their coupling with routes and controllers has made them hard to inject into re-usable components across applications, as I find controllers and routes to be so domain-specific to a given problem.

Other alternatives include Ember Parachute, which provides a QP state object on the controller (rather than as a service). This has been immensely useful and acts as strong evidence in support of this RFC.

filter / search components could update the query param property.

On top of this example in the RFC, I also 100% agree with @lougreenwood's points. I have on many occasions needed to bind query param state to individual models, which has led to some wild services full of "forbidden" observers. At one point, I prototyped a custom Location object for handling dynamic query params - but this was hard to do because I could not hook into the internals of how Ember serializes URLs and updates state (probably for good reason).

Should it be possible to bind to a query param? (from components, from routes)

I thought it is possible currently?

We should also loop in @poteto and @offirgolan who have worked on this problem quite a lot.

@mehulkar

This comment has been minimized.

Copy link

commented Sep 28, 2018

It would be good if the source of the QP (i.e. the controllerName) was somehow exposed also.

(I totally agree that queryParams are an application level concern and should be treated as such, but that discussion sounds like it's either out of scope for this RFC or would invalidate this RFC).

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 28, 2018

Personally, I see no reason why scope couldn't be increased. Bigger changes would just require more planning and include deprecations and interoperability and such.

Question though, why would you want the controller name?

@mehulkar

This comment has been minimized.

Copy link

commented Sep 28, 2018

Personally, I see no reason why scope couldn't be increased.

It would be a totally different rfc to make QPs global and not tied to controllers. Of course it's fine to use this particular document to add all of that in, but I think you'd have to take several steps back to have that conversation.

Question though, why would you want the controller name?

Mainly because the location of the definition is an integral part of how QPs work today. If nothing was changed there, I wouldn't want to lose that context when accessing a QP in some component. It would be weird to "forget" the part of the route hierarchy that a particular QP belongs to, just because you're accessing it in an arbitrary place.

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Sep 28, 2018

I must clarify,

I mostly just want conversation to grow more freely, in case something important otherwise would be stifled for being out of scope.

I do understand that the more that goes in to an RFC, the harder it'll be to get people to agree to it.

Maybe https://discuss.emberjs.com would be better for ideas and other things not directly related to what is proposed here.

NullVoxPopuli added some commits Sep 28, 2018

Update 0000-router-service-computed-query-params.md
 - Make the design a little clearer
   - specify that the query params must have a dependent key on the route name
 - add code to "How we teach this" for before and after the RFC
@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 11, 2019

please re-review at ya'll's leisure <3

I added more examples, clarity, and an addon with a caveat about some currently enforced restrictions, that are.. imo, somewhat unneeded. :)

Also, this is related, but not at all required for this queryParams feature: #499

@sandstrom

This comment has been minimized.

Copy link

commented Jun 11, 2019

I definitely think query params can be improved upon, and I agree that it could be useful to move away from controllers. That said, I think this RFC could flesh out some more details + add some examples on how this would work. I also think it would make sense to discuss alternatives to a dedicated query param service.

I've also posted some questions and thoughts earlier in this thread: #380 (comment)

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 11, 2019

sure thing, @sandstrom here are answers to each of those questions:

How to handle stickiness? [a good, existing, feature]

Query Params are stored per-path: https://github.com/emberjs/rfcs/pull/380/files#diff-53dcace8a656f28a366ac6e64862d2e6R150
(the intent is for this to cascade, so you want query params on / and /foo, both sets will be present when navigating to /foo)

Should it be possible to bind to a query param? (from components, from routes)

yes, via tracked decorator. https://github.com/emberjs/rfcs/pull/380/files#diff-53dcace8a656f28a366ac6e64862d2e6R289

Should read/write go through the same methods, or maybe all writing (updating) should go through transitionTo? [I think the current functionality of setting QPs via controller has some issues

Can you expand on this?

How about serialization (of keys, values)? [a good, existing, feature]

The current implementation of the addon used in the RFC uses the qs library. but serialization / deserialization can be handled most granularly like this:

import Component from "@glimmer/component";
import { queryParam } from "ember-query-params-service";

export default class SomeComponent extends Component {
  @queryParam({
    deserialize: (qp) =>  parseInt(( qp || '' ).replace(/-/g, '')),
    serialize: (value) => `-${value}-`,
  })
  foo;

   addToFoo() {
    this.foo = (this.foo || 0) + 1;
  }
}

_from https://github.com/NullVoxPopuli/ember-query-params-service _

The RFC mentions serialization/deserialization, but not clear how to set it up for all routes/query-params, i.e. in the proposed service.

not all query params should be treated the same. by default everything is strings.

How to handle default values? [currently hidden in url, but the query param key/value exist, which is great in my opinion]

Either use a getter, as you would with default values today with glimmer components

get myProperty() {
  return this.foo || 'default';
}

or it wouldn't be too hard to support this:

@queryParam foo = 'bar';

(which I think I'm a fan of, and will add to ember-query-params-service)

When are query params set (lifecycle-wise)? (both when entering route via 'url' [page load] and via transition]

Related to the previous question, if there are bindings, when are they updated (before/after the model hook has resolved?, something else?)

good question. The API docs don't say: https://api.emberjs.com/ember/3.10/classes/RouterService/events/routeDidChange?anchor=routeDidChange
will have to investigate.

Should some concerns be split? For example, the 'stickiness' of QPs could be handled at the route level by remembering the last value when the route is exited, stored in some storage service, and then restored when the route is accessed again. Should this be the responsibility of the user, or the framework? and even if it's part of the framework, how should that be handled?

based on how tricky it was to re-apply stored query params, I think it should def be a responsibility of the framework.

How to forbid multiple active routes (ancestor1 -> ancestor2 -> leaf) from sharing the same QP name? (name collisions)

we can add runtime assertions, but is this something that happens today? or a feature you'd like added?
at present, I think the current way I'm thinking about implement this is that the leaf~est set of query params will override the ancestor's query params in the event of a collision.

How to handle serialization from e.g. numbers to strings? (qp values) I.e. should qp's have a native "type" (string/number/etc)?

import Component from "@glimmer/component";
import { queryParam } from "ember-query-params-service";

export default class SomeComponent extends Component {
  @queryParam({
    deserialize: (qp) =>  qp ? parseInt(qp) : undefined,
    serialize: (value) => `${value}`,
  })
  foo;

   addToFoo() {
    this.foo = (this.foo || 0) + 1;
  }
}
@sandstrom

This comment has been minimized.

Copy link

commented Jun 11, 2019

Thanks for extensive answers!

  • Regarding serialization, there is currently a serializeQueryParamKey property that can be set on e.g. BaseRoute (which all other routes in an application can inherit from). We use serializeQueryParamKey: Ember.String.dasherize to make sure query params are my-query-param instead of myQueryParam. Some facility for this would be useful (without having to set a serializer per query param).

  • Something like @queryParam foo = 'bar'; would be very useful. Or some other mechanism for default values. Also, presumably a default value wouldn't result in any query param in the url (just as it is today).

  • Should there be a config for toggling stickiness? There is currently a scope property for this (docs).

  • I think there are currently assertions making sure that a two routes (that can be active at the same time) cannot define queryParams with the same name. If we change this to have the leaf-routes override query params in ancestors, I think there is some risk to subtle bugs. Warnings/exceptions/assertions (at least in dev) has some benefits here.

  • Regarding serialization, I think it makes sense to use strings by default. But being able to set serialization globally would be useful, and could be used to serialize to/from another type. Also, today values aren't always serialized to strings.

  • You also asked me to expand on "Should read/write go through the same methods, or maybe all writing (updating) should go through transitionTo?". Basically my question is whether this service should be read-only, or if it should be able to set/update query params too? I think read-only may be easier to reason about, i.e. all query param changes need to go through a transitionTo call. But if we allow writing, how would we handle qp changes while we're in beforeModel, model and afterModel, or other route hooks? Because writing would trigger a transition.

  • Has there been any discussion on whether this should be in a dedicated service, or in the router service? To me it would feel more natural if this was part of a router service.

  • What is the core team's stance/input on this RFC (just curious)?

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 11, 2019

Regarding serialization, there is currently a serializeQueryParamKey property that can be set on e.g. BaseRoute (which all other routes in an application can inherit from). We use serializeQueryParamKey: Ember.String.dasherize to make sure query params are my-query-param instead of myQueryParam. Some facility for this would be useful (without having to set a serializer per query param).

Would this current API be sufficient?

import Component from "@glimmer/component";
import { queryParam } from "ember-query-params-service";

export default class SomeComponent extends Component {
  @queryParam('some-hyphenated-query-param', {
    deserialize: (qp) =>  qp ? parseInt(qp) : undefined,
    serialize: (value) => `${value}`,
  })
  foo;

   addToFoo() {
    this.foo = (this.foo || 0) + 1;
  }
}

Something like @QueryParam foo = 'bar'; would be very useful. Or some other mechanism for default values. Also, presumably a default value wouldn't result in any query param in the url (just as it is today).

Cool, I'll implement this in the addon.

Should there be a config for toggling stickiness? There is currently a scope property for this (docs).

Not sure how this would work yet. Can you link to the existing docs where this is?
I imagine configuration that goes on the route could stay on the route though.

I think there are currently assertions making sure that a two routes (that can be active at the same time) cannot define queryParams with the same name. If we change this to have the leaf-routes override query params in ancestors, I think there is some risk to subtle bugs. Warnings/exceptions/assertions (at least in dev) has some benefits here.

What happens today? I can investigate this too, just wondering if you know off hand before I spin up a bunch of throw-away projects ;)

Regarding serialization, I think it makes sense to use strings by default. But being able to set serialization globally would be useful, and could be used to serialize to/from another type. Also, today values aren't always serialized to strings.

I think, from an API size perspective, it'd be better to encourage composition with something like @pzuraq's https://github.com/pzuraq/macro-decorators. You could make @queryParamNumber, @queryParamArray, etc. This'd allow for repeated conciseness in components/wherever. From the above example, @queryParamNumber foo = 0 would encapsulate the serialization/deserialization functions.

You also asked me to expand on "Should read/write go through the same methods, or maybe all writing (updating) should go through transitionTo?". Basically my question is whether this service should be read-only, or if it should be able to set/update query params too? I think read-only may be easier to reason about, i.e. all query param changes need to go through a transitionTo call. But if we allow writing, how would we handle qp changes while we're in beforeModel, model and afterModel, or other route hooks? Because writing would trigger a transition.

I was under the impression that a transition is always triggered when you change the URL.
I def think writing should be allowed, because it feels ergonomic. Even if a transition would be triggered. But I could go either way on this. There is currently a limitation with controllers deny-listing any query-params not specified on controllers anyway, so This would be something to explore during implementation. But I'd like to try to stick with whatever the current behavior is of transitioning (or not) during a query param change.

Has there been any discussion on whether this should be in a dedicated service, or in the router service? To me it would feel more natural if this was part of a router service.

My preference would be to throw this on the router service, as query params are tied to each route path.
For my addon, I couldn't reasonably add things to the router service without some hackery, so... that's where the query params service came about. :)

What is the core team's stance/input on this RFC (just curious)?

I sent a message to @ef4 and @jenweber, and I hope this and the controller RFC are discussed during whenever the next core team meeting(s) are :)


My current todos:

@amyrlam amyrlam referenced this pull request Jun 11, 2019
16 of 37 tasks complete

@NullVoxPopuli NullVoxPopuli changed the title Add @queryParam decorator and QueryParamsService Add queryParams to the router service Jun 11, 2019

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 11, 2019

Some major updates due to some feedback, and realizing IE11 doesn't support the exact implementation in ember-query-params-service:

  • queryParams object accessible on the router service
  • removed the @queryParam decorator for now, it'll be proposed in a follow up RFC, and can be implemented in a very simple addon, should this RFC be accepted and implemented.
  • intermediate state between routes is still managed by controllers (to be addressed in a follow up RFC)

For those who have commented, and asked questions thus far, should this RFC be accepted and implemented, I'll have a supplemental addon that implements @queryParam until a @queryParam RFC is written, reviewed, and hopefully accepted ;)
(it'll have all the features discussed here, especially per-param (de)serialization)

NullVoxPopuli added some commits Jun 11, 2019

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 12, 2019

Maybe this is more of a design-time question than an RFC one, but I recently found out about URLSearchParams / https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams

which has all the capabilities of qs (except for nested structures, which are a hot mess between servers anyway...).
Should the router.queryParams object be an URLSearchParams instance? or something that is like an URLSearchParams instance, since we may want auto tracking on the queryParams values?

@pzuraq

This comment has been minimized.

Copy link
Contributor

commented Jun 13, 2019

On the one hand, I like that there's a browser API we could leverage, and it would mean fewer kbs shipped to the browser in general. On the other hand, I feel like arrays are a pretty common use case for QPs that users would run into, and objects are not entirely uncommon. There are a lot of different ways to do that though.

I think this system needs to be pluggable, and the default should be the browser API (or a polyfill for IE11), URLSearchParams. User should be able to add their own serializer/deserializer that transforms the query string into the router.queryParams object, and updates the query string with new values. This is probably in scope for this RFC, though it could possibly be done as a followup.

@pzuraq

This comment has been minimized.

Copy link
Contributor

commented Jun 13, 2019

The latest draft of this is looking great 😄 One quick point:

controllers will still manage the long-term state of the query params as they do today

I don't think it would be a good idea for controllers to manage the state of query parameters that aren't defined in the queryParams array on the controller. Using controllers to manage the long term state of a QP locks us further into the controller's statefulness, and is something that's going to prevent people from dropping them and replacing them with components (like in #499). Instead, if a QP is not defined on the controller, it should probably be considered global state. Then the transition path looks like:

  1. Begin using router.queryParams for things relating to QPs locally
  2. Add your own memoization logic if needed, and remove QP from the controller definition
  3. (Eventually...) Drop the controller entirely
@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 13, 2019

@pzuraq

User should be able to add their own serializer/deserializer that transforms the query string into the router.queryParams object

maybe this is something configurable on the router?

import EmberRouter from '@ember/routing/router';

import config from '../config/environment';

const Router = EmberRouter.extend({
  location: config.locationType,
  rootURL: config.rootURL,

  queryParamsConfig: {
    serialize(queryParams: object): string {
    // default to URLSearchParams, polyfilled for 
    },
    deserialize(queryString: string): object {
      // parse to object for `injectedRouter.queryString`
    }
  }
});

Instead, if a QP is not defined on the controller, it should probably be considered global state

totally agree 👍

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 13, 2019

From a discussion @pzuraq and I had on Discord:

Given this example:

Router.map(function() {
  this.queryParams('bar');

  this.route('faq');

  this.route('posts', function() {
    this.queryParams('foo', 'baz');

    this.route('new');
    this.route('index');
    this.route('show');
  });
})

This gives the ability to define sticky query params.
Meaning:

  • all query params, even if not specified are allowed. given the above example, I could use the qp "strongestAvenger" anywhere
  • any sort of transition will clear the query params, unless it is defined as a sticky queryParam. So, if I'm on the posts/new route with the query params "foo" and "baz", and transition to posts/show, those query params are still available. but if I navigate to the faq page, foo and baz are cleared from the URL.
  • the globally defined query params will stick around until cleared manually. If I visit faq?bar=2, and then transition to posts. the bar=2 query param will still be present.
  • this.queryParams will be available at every nesting of the route tree.

Notes:

  • this does not imply a caching mechanism (like what controllers today allow)
  • If a certain app wants caching of query params as they exist today, an addon could be made to fill that gap. Maybe even ember-query-params-service could be fixed/repurposed a bit to take care of that need.

I'll be updating the RFC within the next day or so with this new info :)

@NullVoxPopuli

This comment has been minimized.

Copy link
Contributor Author

commented Jun 14, 2019

Just did some edits and updates :)

import Component from "@glimmer/component";
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
If query params are defined ahead of time as sticky, they will persist in the URL between sub routes.

This comment has been minimized.

Copy link
@sandstrom

sandstrom Jun 17, 2019

My interpretation of this is that sticky query params [in this RFC] remain when moving between sibling routes. But query params as they work today is that they are cached and 'restored' when leaving and then re-entering the route (but not visible in between).

If the above is correct, it seems like you are giving the word 'sticky' another meaning than it currently has. Perhaps another word could be used, to avoid confusion?

This comment has been minimized.

Copy link
@NullVoxPopuli

NullVoxPopuli Jun 17, 2019

Author Contributor

I'd like to argue that the current usage in the guides is incorrect, and it should be 'persistent' or 'persisted'.

But idk how hard of a shift that'd be.

This comment has been minimized.

Copy link
@sandstrom

sandstrom Jun 17, 2019

That could very well be the case, but anyway I'd clarify that sticky means different things in this RFC and the current version of ember. To avoid confusion.

This comment has been minimized.

Copy link
@rwjblue

rwjblue Aug 23, 2019

Member

I agree, this should be addressed. The usage here in this RFC is quite different from what we have used the term "sticky" to mean historically.

@service router;
get currentPage() {
const { page } = this.router.queryParams;

This comment has been minimized.

Copy link
@chadhietala

chadhietala Aug 9, 2019

Member

Why do we need to expand the surface area of the Router Service? There is already currentRoute.queryParams on the service.

This comment has been minimized.

Copy link
@NullVoxPopuli

NullVoxPopuli Aug 9, 2019

Author Contributor

I think the intent is that queryParams just.. are and aren't tied to a specific route.
currentRoute.queryParams, at least to me, implies that other routes could have different set's of query params.

That said, I don't see a reason why currentRoute.queryParams couldn't be the place to get query params -- I just think it implies unwanted things.

This comment has been minimized.

Copy link
@NullVoxPopuli

NullVoxPopuli Aug 9, 2019

Author Contributor

Maybe some third party cacheing addon could provide different query params per route?

Update text/0000-router-service-computed-query-params.md
Co-Authored-By: Jan Bobisud <me@bobisjan.com>
text/0000-router-service-computed-query-params.md Outdated Show resolved Hide resolved
Router.map(function() {
this.queryParams('bar');
this.route('search', { queryParams: [

This comment has been minimized.

Copy link
@rwjblue

rwjblue Aug 23, 2019

Member

Lets format this with prettier (and the other example snippets), I think it would end up something like:

this.route(
  'search',
  {
    // list here
  },
  function() {

  }
);
import Component from "@glimmer/component";
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
If query params are defined ahead of time as sticky, they will persist in the URL between sub routes.

This comment has been minimized.

Copy link
@rwjblue

rwjblue Aug 23, 2019

Member

I agree, this should be addressed. The usage here in this RFC is quite different from what we have used the term "sticky" to mean historically.

@rwjblue

This comment has been minimized.

Copy link
Member

commented Aug 23, 2019

This RFC should include a bit more detail:

  • a comprehensive list of the ways the current QP system can be used (the use cases at least), and how you would do the same things in the new APIs
  • examples of how we would teach the new API (e.g. for the guides)
  • examples of how current QP related addons would plausibly function over the new APIs
@rwjblue

This comment has been minimized.

Copy link
Member

commented Aug 23, 2019

I'd also like to see an exploration of why the core has to do this at all, what would a minimal API look like to allow this entire RFC to be implementable in user space? From my perspective, there are a few fundamental things that might unblock that:

  • adding a public API to access Router.map defined metadata
  • adding a hook that can be used to customize the target of all .transitionTo / .replaceWith invocations (intercepting the QP properties, and setting state on the QP service)
  • adding a hook that can be used to customize how a URL is translated back into router params (intercepting the URL string and setting state on the QP service)

I'm willing to accept that this might be naive, but it seems like an interesting (smaller, more customizable, and easier to implement) alternative...

- All query params, even if not specified are allowed. given the above example, I could use the qp "strongestAvenger" anywhere
- Any sort of transition will clear the query params, unless it is defined as a sticky queryParam. So, if I'm on the posts/new route with the query params "foo" and "baz", and transition to posts/show, those query params are still available. If I navigate to the faq page, foo and baz are cleared from the URL.
- The globally defined query params will stick around until cleared manually. If I visit faq?bar=2, and then transition to posts. the bar=2 query param will still be present.
- The `this.queryParams` function will be available at every nesting of the route tree, but `queryParams` are also configurable in the route options hash.

This comment has been minimized.

Copy link
@mehulkar

mehulkar Aug 23, 2019

This is a little concerning. I'd prefer there to be only one way to do this. Maybe it should always be as an option on this.route(), with an option for whether or not it cascades down to child routes?

this.route('search',
  {
    queryParams: {
      names: ['foo', 'bar', baz'],
      cascades: false
    }
}

This comment has been minimized.

Copy link
@rwjblue

rwjblue Aug 23, 2019

Member

I'd also prefer to avoid adding this.queryParams to the context of each callback on the Router.map. Passing configured query params as metadata seems better (and allows better interop with other things that also want to be router map metadata).

This comment has been minimized.

Copy link
@mehulkar

mehulkar Aug 23, 2019

Another open question from this.queryParams() is the order of declaration and if that matters. Since the order of declaration of this.route() does matter, I'd want a spec for what these variations do:

this.queryParams('search');
this.route('foo');

vs:

this.route('foo');
this.queryParams('search');

This comment has been minimized.

Copy link
@NullVoxPopuli

NullVoxPopuli Aug 23, 2019

Author Contributor

@rwjblue @mehulkar

I'd prefer there to be only one way to do this.

without this.queryParams, there'd be no way to set global query params, unless we started adding this.route('application') by default -- which may be fine

order of declaration and if that matters.

there is no difference in order with this.queryParams as it's just a function-scoped invocation, and applies to the route-scope of this (application at the top level)

```ts
@service router;
this.router.setQueryParam('term', 'RTX');

This comment has been minimized.

Copy link
@mehulkar

mehulkar Aug 23, 2019

An alternative here is to have this.router.queryParams.set('term', 'RTX'). I get that there's value in queryParams being a POJO, and that introducing a custom type here may introduce hurdles for people implementing their own serialize/deserialize hooks, but defining a more strict interface may also be a good thing in the long run (similar to URLSearchParams, for example)

This comment has been minimized.

Copy link
@NullVoxPopuli

NullVoxPopuli Aug 23, 2019

Author Contributor

I'd like to stay away from set/get as they're not really present in idiomatic Octane.

but defining a more strict interface may also be a good thing in the long run

I think being simple first is more important here, as the current query params are too restrictive and there are too many unknowns with (de)serialization

Update text/0000-router-service-computed-query-params.md
Co-Authored-By: Robert Jackson <me@rwjblue.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.