Ember.js change tracked variable in child component

Viewed 80

I am new to ember.js and wanna know what's the best way to alter @tracked variable in child component.

Assume this structure:

Parent
  --- Component1
  • Parent controller

    @tracked
    number = 1
    
  • Parent.hbs

    <Component1 @number={{this.number}} />
    {{this.number}} 
    
  • Component1.hbs

    <button {{on "click" this.addNumber}}> Add </button>
    
  • Component1.js

    @tracked
    number = this.args.number
    
    @action
    addNumber(){
        this.number += 1;
        console.log(this.number)
    }
    

The number did addon in the Component1 and print the number every time I clicked the Add button, however, it didn't reflect to Parent level.

1 Answers

welcome to Ember!

It looks like you're wanting some form of two-way binding -- which doesn't exist in Ember.

In order to update data on a parent "something", you'll need to define a function to update that data.

For example:

// parent.js
export default class Parent extends Component {
  @tracked number;

  updateNumber = (nextValue) => this.number = nextValue;
}
{{! parent.hbs }}
<Component1 
  @number={{this.number}} 
  @updateNumber={{this.updateNumber}} 
/>

And then in Component1:

// component1.js
export default class Component1 extends Component {

  addNumber = () => {
    let { updateNumber, number } = this.args;
    
    updateNumber(number + 1);
  }
}
{{! component1.hbs }}
<button {{on "click" this.addNumber}}> Add </button>

Note that only the "source of truth" of the data needs to be declared as tracked. Any other data involving number does not need to be tracked. Data using number can be "derived" with getters. For example:

// in component1.js
get doubled() {
  return this.args.number * 2;
}

No need for decorators or anything -- this vanilla - no-need-to-compile JS.

A note on something I noticed in your example:

// component1.js
class Component1 extends Component {
  @tracked
  number = this.args.number

  // ...
}

property assignment in the body of a class only happens once, during instantiation. So, if this.args.number happened to be 2 at the time of instantiation, the above would be equivelant of:

// component1.js
class Component1 extends Component {
  @tracked
  number = 2

  // ...
}

which effectively detaches the reactivity. It is a handy trick though if you want to manage state locally in the component.

Docs on component state can be found here: https://guides.emberjs.com/release/components/component-state-and-actions/

And an interactive demo / repl here (using the next-gen syntax (polyfilled via ember-template-imports))

Related