Query params @tracked

I am switching the Bloggr example to Octane but was wondering if I need to add @tracked to https://github.com/broerse/ember-cli-blog/blob/master/app/controllers/posts.js for it seems to work perfect without it. Is there a @tracked query param rule of thumb?

The rule of thumb is very roughly:

Mark everything you own as @tracked; trust the framework for things it owns.

So, for example, query parameters (even though we set them up) are things the framework owns: it manages them based on the state of the URL. (They’re also two-way bound, which is the underlying reason why they work without being tracked). Likewise, you don’t mark Ember Data model properties as @tracked, because they’re configured for tracking via the other decorators @attr, @belongsTo, @hasMany.

On the other hand, if you’re defining a session service, you own the data:

import Service from '@ember/service';

export default class Session extends Service {
  @tracked user;  // we own this!

  login(username: string, password: string) {
    fetch('https://example.com/login', { method: 'GET' })
      .then(data => data.json())
      .then(user => {
        this.user = user;  // we set it here
      .catch((e) => {
        alert("Whoops! Couldn't log in!");

  logout() {
    this.user = null;

The same thing goes for components, non-framework-owned properties on controllers or models or Ember Data models, etc.

1 Like

If that’s true then someone should update the Guides, as they show QPs being tracked:

And this is what it says on the page:

Now we need to define a getter for our category-filtered array, which the articles template will render. For the getter to recompute when values change, category and model should be marked as tracked properties:

Interesting. Because of how tracking works, adding the annotation won’t break it, but there are definitely weird cases like this around query parameters specifically.

I’ve converted to Octane and, like you said in your initial response, non-tracked QPs worked fine for me.

Thanks. It seems I don’t understand 100% why @value={{query}} does not need a @tracked anywhere. Even if I don’t make it a query param it still works fine. See:

This is also true for page and perPage. I would say the rule of thumb has not landed in my head but I am sure it will soon :grinning:

Arguments for Glimmer components are always tracked!

1 Like

As @chriskrycho says above, arguments of your Glimmer components are always tracked so I think that’s why <Input @type="text" @value={{query}} ... /> works in your component.

I checked and you’re calling the component as follows:

<BlogPosts @posts={{model}} @page={{page}} @perPage={{perPage}} @query={{query}} @createAction={{action "createPost"}}>{{outlet}}</BlogPosts>

I’m not sure, though, as you have @value={{query}} and I’m not sure what query refers to here. For reasons of compatibility, it’s the same as writing @value={{this.query}} but afaict you don’t have a query property defined in the JS class of the BlogPosts component. That makes me think Ember still copies over component arguments to component properties and that’s why the above works at all. (It should be @value={{@query}})

However, I think one of the best things in Octane is clarity in templates so I suggest to never have something without a this or @ in your templates. If it starts with this, it must come from the backing class, if it starts with a @, it’s a component argument and should be passed in.

Just wanted to chime in here to confirm that this does not happen. When using @glimmer/component as your base class, the only properties that will be set on the component instance by the framework are:

  • args
  • isDestroying
  • isDestroyed
  • the symbol used to power getOwner(this)

Thanks, Rob, that’s great to know.

I realize now the component in question extends from @ember/component and that’s why it works.

@broerse You probably want to convert your components to Glimmer components to make them “Octane-friendly” .

Thanks everybody. I will convert to Glimmer components too. The @tracked rule of thump seems to be more an If - Then - Else schema that has not landed with me yet.

I had to add @tracked on the controller to pass the tracked queryParams to Glimmer components like this: (https://github.com/broerse/ember-cli-blog/commit/f0aefe30b12844208436a70a395fd0b8febe09fc)

class QueryParamsObj {
  @tracked page = 1;
  @tracked perPage = 5;
  @tracked query = '';

export default class PostsController extends Controller {

  @alias ('queryParamsObj.page') page;
  @alias ('queryParamsObj.perPage') perPage;
  @alias ('queryParamsObj.query') query;

  queryParams= ["page", "perPage", "query"];
  queryParamsObj = new QueryParamsObj();


Still have to wrap my head around it but looks to me like @tracked Closure QueryParams.

Thanks for helping me get this figured out.

So it didn’t work without defining them as in pre-Octane Ember?

export default class PostsController extends Controller {
  queryParams = ['page', 'perPage', 'query'];

  page = 1;
  perPage = 10;
  query = '';
  // (...)

No it didn’t. @tracked is needed to add get & set to the query params . The Closure seems needed to pass the query parmas to ember-cli-pagination or ember-cli-filter-by-query etc

The example @balint gave worked for me. This is what I have:

export default class Acts extends Controller {
queryParams = ['filterActs', 'searchActs'];

filterActs = '';
searchActs = '';
// (...)

It only worked for me as long as I didn’t use import Component from '@glimmer/component';

Also query has no get and set defined on them needed for ember-cli-filter-by-query. Adding @tracked solved this for me.

You can see this on https://github.com/broerse/ember-cli-blog

It goes against @chriskrycho 's rule of thumps but without @tracked for example, this is not working.

  @action resetPage() {
    this.args.queryParamsObj.page = 1;

It can be sometning I am doing wrong. Still learning @tracked

All of my imports are import from '@glimmer/component'

And I know what you mean about still learning… me too!

You should in general not be mutating state you don’t own. You can do this kind of thing (though it’s not currently clear to me why this would work if you were using @tracked on queryParams on the controller?) but in general it’s a bad plan to mutate the state remotely like this instead of letting the controller itself manage that state.

Another good rule of thumb here is: only the code which marks something as @tracked should change it. (That’s not a hard and fast thing, as it’s idiomatic to change state of Ember Data models and .save() them, for example, but it’s a good rule of thumb.)

The reason I’m confused here is that @tracked does not deeply track POJOs:

import { @tracked } from '@glimmer/tracking';

export default class Example {
  @tracked pojo = { neat: true };

Here, pojo is tracked but pojo.neat is not. It would be very expensive to do that by default, and often you don’t want it. For these scenarios, you can do one of two things:

import { @action } from '@ember/object';
import { @tracked } from '@glimmer/tracking';

export default class Example {
  @tracked pojo = { neat: true, cool: 'beans' };

  // mutate and "notify" by re-assigning itself
  @action mutateNeat(newValue) {
    this.pojo.neat = true;
    this.pojo = this.pojo;

  // new object, "immutable functional update"
  @action immutableUpdateNeat(newValue) {
    this.pojo = { ...this.pojo, neat: newValue };

Notably, though, this makes the QPs behavior… even weird.

@pzuraq, can you explain what’s happening here?

Query params are a bit weird, because there are so many different ways to interact with them. The framework manages them using get and set internally, which is why generally, if you only interact with them using transitionTo, you’ll be fine.

However, if you do want to set them directly, you need to use @tracked like @broerse is suggesting, if I remember correctly. It’s debatable whether those properties are framework-owned or user-owned, but we figured that the hint of @tracked provided explicitly by the user would be better than magically tracking the properties automatically.