Why do I not need to mark the property as tracked?

Howdy,

I have a somewhat classic piece of functionality: I have a (native) text input that needs to update a property on its context, as follows:

<div class="mt-1 relative rounded-md shadow-sm">
  <input
    id="name"
    class="w-full text-gray-800 bg-white border rounded-md py-2 px-4"
    value={{this.name}}
    {{on "input" this.updateName}}
  />
</div>

The context has the dead simple updateName function:

export default class BandsNewController extends Controller {
  @action
  updateName(event) {
    this.name = event.target.value;
  }
}

This works – this.name is kept in sync with the value of the text input which really surprises me.

If I use a regular JS set operation (the = sign) with a property that is used in the template, I expect to get the following error:

Uncaught Error: Assertion Failed: You attempted to update [object Object].name to “TOOL”, but it is being tracked by a tracking context, such as a template, computed property, or observer. In order to make sure the context updates properly, you must invalidate the property when updating it. You can mark the property as @tracked, or use @ember/object#set to do this.

If I mark name as tracked, it still works (which is a relief), but I’m not sure why it does without the property being marked as tracked.

I went on trying to explore the difference by showing the value of this.name in the template:

{{this.name}}
<div class="mt-1 relative rounded-md shadow-sm">
  <input
    id="name"
    class="w-full text-gray-800 bg-white border rounded-md py-2 px-4"
    value={{this.name}}
    {{on "input" this.updateName}}
  />
</div>

In this case, if name is marked as tracked, it duly updates {{this.name}} in the template. If it’s not marked, the template renders an empty value (its original value) so the value is not updated in the template.

I wonder if not throwing an error is maybe a bug? If not, what’s the rationale behind it?

Thank you.

1 Like

I can’t speak to why the error is or is not thrown, but your further example gets at what’s actually happening the first instance: it’s not updating the value from the perspective of downstream consumers. When you do value={{this.name}} on the input, but {{name}} is not tracked, you’re basically setting it to that value once, but in a way that is disconnected from further updates. The event handling with {{on}} sets that property on the backing class, but the input would update its value regardless—it’s just not reflected back into the backing class’ internal state unless you use {{on}} or similar. It appears to “work” here simply because input will happily go on doing its normal thing, entirely ignorant of the JS on the page.

I’m also curious why it isn’t triggering that error, though!

2 Likes

I wonder if not throwing an error is maybe a bug? If not, what’s the rationale behind it?

This was changed for 3.20 in [FEATURE] Scopes down the mandatory setter by pzuraq · Pull Request #18961 · emberjs/ember.js · GitHub. I’m pretty sure I couldn’t explain better than what @pzuraq did in that issues description.:laughing:

1 Like

@chriskrycho’s explanation of why the <input> itself visually appears to be updated (that you are really just seeing the elements internal state, not anything to do with a top down re-render).

Ah, this makes sense and I should have remembered it! The quest referenced there sums up why we needed to remove this:

the mandatory setter is no longer particularly helpful in a number of cases.

In particular, it prevents using autotracking in a bunch of places where it should work fine and is very useful, like with the immutable data structures library Immer—and it had a number of other issues besides, as the quest describes.

While the intent of the assertion was to be helpful, it was impossible to get right across the board, and in the auto-tracking world it’s much easier to just explain that if your property isn’t updating its simply because your root state isn’t properly tracked. There are many fewer places to fail than there were in the old dependent-key world where you could have many intermediate points of failure with missed keys, etc.

3 Likes

@chriskrycho Thanks a lot for your replies, let me sum it up to make sure I understand.

  • In Ember versions 3.20 and above the mandatory setter warning only applies (gets triggered) in “classic APIs”: dependencies of computed properties and computed property macros, and properties watched by observers. This is so that adding the mandatory setter (under the hood) can be applied uniformly and that tracked properties can be used with 3rd party libraries, like Immer.
  • One still needs to mark properties that need to update in the template as tracked, as in both of my examples above.

Is that correct?

1 Like

Yes, that is correct.

1 Like

To add slightly more color/context here, it isn’t just about external 3rd party libraries (though they do certainly benefit). With the assertion in place certain things are actually impossible to do, for example what if you have a value that you use in a template, need to update via some action or something, and don’t want the template to re-render?

2 Likes

Great point. Is simply not marking the property as tracked the “one true way” to do it?

1 Like

Right, as of 3.20 you can decide to leave off @tracked on purpose and it will still work (initial render will grab whatever the value happened to be at the time, and the value will never update). Prior to that you could have tried something like {{unbound}} but it has a large number of quirks.

2 Likes