Passing arguments to component - variable reference

I’d like to clarify something related to passed arguments to component.

Assume we have (pseudo code):

  • model of product:

…/model/product.js Product { String description }

  • child component: …/product-container.hbs …/product-container.js

  • parent template with resolved model:

…/application.hbs

<ProductContainer @description={{this.model.product.description}} />

The question is:

what kind of assignment in …/product-container.js

should it be:

@tracked description = this.args.description

Is it copy or reference? Shouldn’t this.description = “foo” change product.description in ember data?

Rather than writing up a detailed answer here, I’ll just link you to a deep dive I wrote on the subject because it comes up often: Understanding args in Glimmer Components. The takeaway:

  • Args are values, not references. If you pass a reference type in it is of course still a reference type, but if you re-bind it like that, you’re basically “disconnecting” it from the arg, and if the parent passes a new reference to it, you will never receive it.
  • As a result, if you want the reactivity system to work correctly, you should basically never assign @tracked description = this.args.description: you’re not creating a two-way binding by doing that; you’re creating new reactive state in the system, which happens to start with the original value passed in.

Thus, instead of setting this.description in product-container, you would just pass it further down (e.g. <ProductPresenter @description={{@description}} />). Whoever “owns” the state (e.g. the controller) can also pass down actions to update it. Alternatively, you can pass down the model as a whole: the public API of Ember Data models is that all the attributes are reactive state.

Thank you very nice post on your blog. I have one more question. What is the Octane way to remove old Ember’s observers? I think there is a lack in guides, but in my previous job we used to use observers very often.

Assume we have (pseudo code):

child component:

product-container.hbs with external component KindOfInput (e.g. Wysiwig editor like TUIEditor, Froala etc.)

<KindOfInput @value={{@product}} @onChange={{this.changeValue}} />

product-container.js

@tracked numberOfChars

@action this.changeValue(value) { … this.numberOfChars = value.length }

get number() { this.numberOfChars; /and for debuging/ console.log(this.numberOfChars); }

How to run number(), without showing it on the template, to work as observer?

I would avoid observers or anything like them. If you can describe the actual use case here, I may be able to describe a solution!

Assume that we have it:

<TuiEditor
  @height={{this.height}}
  @minHeight={{this.minHeight}}
  @previewStyle={{this.previewStyle}}
  @editType={{this.editType}}
  @value={{this.value}}
  @onChange={{fn (mut this.value)}}/>

It is one property for value - this.value and one event onChange. It uses mut helper to change the value after each input. In our component I’d like to have action that count length of string inside the value. It could be done by :

runnig the second action in onChange event but I didn’t find the way to run two actions from

  • one event or

  • using observer on value property or

  • removing mut helper.

What I did was removing of mut helper. Now I have one action that changes the value and counts length of string in component’s js file. But the question is, is it possible to use mut helper (what is preferred way to write less code for forms) to change the value and run function that e.q. counts length of text in the value?

It really depends on what you’re using the count for. If you’re using it to pass further up the UI tree, then I would just put it in that action, yep! And I think that’s actually much clearer: it keeps the action all in one place rather that having an action implicitly triggered by setting a value somewhere else!

1 Like