This is an Ember addon that gives you a way to reflect state into the search params (also known as "query params") of the URL.
While Ember's built-in router also offers query params support, that support was built for an earlier era of API and isn't as convenient as it could be. This addon is a candidate for how the built-in query params support should work in the upcoming Ember Polaris edition.
- Ember.js v3.28 or above
- Embroider or ember-auto-import v2
-
Install the package:
ember install tracked-params
-
Delegate control over the URL to
tracked-params
by editingconfig/environment.js
like this:const ENV = { - locationType: 'history', + locationType: 'tracked-params-history', + historySupportMiddleware: true, }
We support all the same location types as stock Ember, so you can also say
"tracked-params-hash"
or"tracked-params-none"
.
This addon provides a drop-in replacement for @tracked
called @trackedParam
:
import Component from '@glimmer/component';
import { Input } from '@ember/component';
-import { tracked } from '@glimmer/tracking';
+import { trackedParam } from 'tracked-params';
export default class Example extends Component {
- @tracked q = '';
+ @trackedParam q = '';
<template>
<label>Search Terms: <Input @value={{this.q}} /></label>
</template>
}
It works exactly like tracked
except it also reflects the value as a search param in the URL. In the above example, if the user types "glimmer" in the input field, the URL bar will update to show ?q=glimmer
. If they then refresh the page, the value of q
will be restored from the URL.
- When a
trackedParam
is first instantiated:
- if it finds a valid value already in the URL, it initializes itself from that value.
- otherwise it initializes itself with the initial value you've set in your code. In the above example,
@trackedParam q = ''
means we will default to an empty string if there is not already a value in the URL when we're starting up.
-
Once it's initialized, the trackedParam is the authoritative state. If you want to change it, you mutate the field as usual (like
this.q = "New value"
), and the change will automatically reflect to the URL. -
When a
trackedParam
is destroyed (typically because the component its on is being torn down), it removes itself from the URL.This means that you control the lifetime of your state by placing it in the appropriate place in your hierarchy of components (or models, etc).
If two parts of the app try to control the same search param simultaneously, we throw an exception. Params in the URL are fundamentally global.
A trackedParam
is always a string because that's what goes in a URL. But you can create other types using createTrackedParam()
, and we provide pre-made trackedBoolParam
and trackedNumberParam
as conveniences.
Here's a complete example of a custom param for luxon DateTime's:
import Component from '@glimmer/component';
import { createTrackedParam } from 'tracked-params';
import { DateTime } from 'luxon';
import { on } from '@ember/modifier';
const trackedDateParam = createTrackedParam({
validate(s: string): boolean {
return DateTime.fromISO(s).isValid;
},
serialize(date: DateTime): string {
return date.toISODate();
},
deserialize(s: string): DateTime {
return DateTime.fromISO(s);
},
});
export default class Example extends Component {
@trackedDateParam start = DateTime.now().startOf('day');
get displayDate() {
return this.start.toLocaleString(DateTime.DATETIME_MED);
}
tomorrow = () => { this.start = this.start.plus({ days: 1 }) }
<template>
<div>{{this.displayDate}}</div>
<button {{on "click" this.tomorrow}}>Tomorrow</button>
</template>
}
Our installation instructions tell you to change your application's locationType
. This is Ember's abstraction over the URL. While most people only need to use Ember's built in location types ('history'
, 'hash'
, and 'none'
), using a custom implementation instead of those is a longstanding public API.
Because the Location mediates all reading and writing of the URL, it's possible for an addon like tracked-params
to implement its own policy outside the defaults provided by Ember.
If you don't use one of our provided locationTypes, trackedParam
doesn't error. It gracefully degrades to a normal tracked property. This is a deliberate choice because it makes it easier to render and test components in isolation, when no routing is available or appropriate.
If you already have a custom Location for unrelated reasons, our implementation composes around any existing Location, not just the three Ember built-in ones. For example, if you already have app/locations/custom.js
, you can create app/locations/tracked-params-custom.js
like this:
import { TrackedParamsLocation } from 'tracked-params';
export default {
create(owning) {
return new TrackedParamsLocation(owning, 'custom');
},
};
And then change your locationType
from custom
to tracked-params-custom
.
Our installation instructions tell you to set historySupportMiddleware
. This is needed when you're using locationType: 'tracked-params-history'
because we want ember-cli's development web server to serve the app at all deeply nested URLs (just like it would if we kept using the 'history'
locationType), and by default it will only do that if it sees locationType === 'history'
.
In integration tests, components are rendered in isolation and there is not necessarily a router instantiated around them. In that case, trackedParam
gracefully degrades to a normal tracked property. That is, your code continues to work the same but no actual URL is read or written.
Acceptance tests typically use Ember's none
locationType. If you want to be able to visit('/some?tracked_param=123')
in acceptance tests, change your testing locationType
from "none"
to "tracked-params-none"
.
CAUTION: currentURL()
from @ember/test-hepers
reflects what Ember's router sees as the URL. But we are deliberate not exposing our params to the router, because it won't understand them. So you cannot use currentURL()
to assert about your tracked params.
Possible upcoming additions: test-support utilities so you can
- optionally provide initial values to params in component integration tests
- assert about the current values of the params, both in integration and acceptance tests
See the Contributing guide for details.
This project is licensed under the MIT License.