Ember Octane and 2-Way-Binding. What are you recommend?

Ember 3.20 still has the Input-Template-Component

<Input @value=this.value />

which changes the value of the parent-component? What’s your recommendation for new Octane apps? Should we use the Input-component? Or should we go with native inputs? The second feels more correct, doesn’t it?

<input type="text" value={{this.value}} {{on "change" this.updateValue}} />

Another report: In this scene paramObject is an Ember-Object. Therefore value of paramObject gets changed at the child, although a param (@) is passed.

{{log @paramObject.value this.args.paramObject.value}}
<Input @value=@paramObject.value />

I’d personally shy away from the built-in Input component, and would only use native ones:

<input type="text" value={{this.name}} {{on "input" this.updateName}} />
2 Likes

100% agree, I almost always use native inputs, even pre-octane.

FWIW, this stance is also what caused you to hit Why do I not need to mark the property as tracked? :smiling_imp:

Note, I don’t think it’s particularly bad to use native <input> by default, I just found the two posts funny/ironic when I viewed them together this morning :smile_cat:.

2 Likes

Fair enough :slight_smile: Now that I mostly understand why I needed to mark the property as tracked with a regular input tag (and why it seemed to work without it), I think I still prefer the native input but I get the point: Input “just works”.

Ya, and IMHO the (hopefully) simple mantra of "if I need to react to a change in this value, it should be @tracked" is really powerful.

1 Like

Thanks for your responses! Unfortunately I don’t quite understand it. So, if one uses the native-input, the input-value should marked as @tracked?

Please consider following use-case and please let me know your thoughts :grinning:

//routes/user-edit.js
export default class EditUserRoute extends Route {
  model() {
    // i.e. GraphQL Request, which returns a POJO
    return {
      firstName: 'Michael',
      lastName: 'Knight',
      address: {
        street: 'Schoolstreet 22',
        town: 'New York'
      }
    };
  }
}
//templates/user-edit.js
<UserEdit @user={{this.model}} />
//components/user-edit.js
export default class UserEditComponent extends Component {
  @tracked user = null; // I think this tracked is for nothing, isn't it?

  constructor() {
    super(...arguments);
    this.user = this.args.user;
  }

  @action updateFirstName({ target }) {
     this.user.firstName = target.value; // I know, that {{user.firstName}} is not rerendered if this action is triggered. I just want to know, what's your recommended solution to this issue. Please see below.
   }
}
{{!-- ... --}}
<input placeholder="Firstname" type="text" value={{this.user.firstName}} {{on "input" this.updateFirstName}}>
{{!-- ... --}}
<input placeholder="Street" type="text" value={{this.user.address.street}} {{on "input" this.updateStreet}}>
{{!-- ... --}}
{{user.firstName}}
<br>
{{user.lastName}}
<br>
{{user.address.street}}

Solutions:

  1. Convert the POJO from the model hook into a native Class.
class Address {
  @tracked street;
  @tracked town;

  constructor({ street, town }) {
    this.street = street;
    this.town = town;
  }
}
class User {
  @tracked firstName;
  @tracked lastName;

  constructor({ firstName, lastName, address }) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.address = new Address(address);
  }
}
//routes/user-edit.js
export default class EditUserRoute extends Route {
  model() {
    // i.e. GraphQL Request, which returns a POJO
    return new User({
      firstName: 'Michael',
      lastName: 'Knight',
      address: {
        street: 'Schoolstreet 22',
        town: 'New York'
      }
    });
  }
}
  1. Keep the POJO and use { set } from '@ember/object'
  @action updateFirstName({ target }) { 
     set(this.user, 'firstName', target.value);
  }

For me 1. seems a bit costly. But what is better for the long run? Thanks

I understand @wuarmin because I’m new to Ember and the OFFICIAL guide uses Input yet I see many people discouraging the use of it.

This is a bit of a tricky spot. The use of <Input /> is still well-supported, and it handles a lot of things for you that you otherwise need to implement yourself. However, it’s also tricky because it comes from Ember Classic and does not work the way that a “normal” Ember Octane component would. In a Glimmer component (in Octane), you have to do Data Down, Actions Up. In Classic components, you don’t, and many didn’t. The result is sometimes confusing to people who have only ever worked in the Octane world.

The reason is that if you’re just used to working with Octane components, and you write this:

import Component from '@glimmer/component';

export default class MyComponent extends Component {
  myValue = 'hello';
}
<SomeOtherComponent @value={{this.myValue}} />

—well, you would not expect that SomeOtherComponent could change the value in your backing class. It’s not using @tracked, there’s no action being passed into SomeOtherComponent, so you’d normally assume that SomeOtherComponent cannot change myValue. But Input does change myValue if you do the same thing.

This “gotcha” is why a lot of people in the Ember community now prefer to use the native <input> element and wire it up manually. But as I said at the start of this comment: that’s a lot more work, and <Input> handles a lot of edge cases for you! So there’s a tradeoff here. At some point in the future, Ember might reimplement <Input> to use a Glimmer component and make it work the way you’d expect—but there’s a surprising amount of work to be done there to make that possible, so I wouldn’t expect to see that any time soon.

In the meantime, you can (and even should) confidently use the existing <Input> component if it solves your needs—just be aware that its behavior is different from other parts of the Octane experience.

4 Likes

Thanks @chriskrycho that was well explained. I wish the guides provided more context when referencing things that belong more to the Ember Classic way than the Octane way.

I think in general there needs to be a way to mention DDAU. Seems it’s a big part of Ember Octane.

2 Likes

thank you @chriskrycho for the explanation. That helped a lot. With that knowledge, I will use the <Input> component.

1 Like

In the meantime, you can (and even should ) confidently use the existing <Input> component

@chriskrycho If you need to handle oninput event of an input, there’s no other “Octane”-way as using the native input. Am I right?

<input
   value={{this.filterValue}}
   {{on "input" this.filter}}
>

Should this.filterValue should be marked as tracked in following case?

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

export default class FilterComponent extends Component {
  @tracked filterValue = null; // is this necessary

  @action
  filter({ target }) {
    //imagine the use of ember-concurrency to wait i.e. 40 ms before calling onFilter(value)
    const { value } = target
    this.filterValue = value;
    this.onFilter(value);
  }
}

That’s correct. The modifier is the Octane way of doing it—and it works nicely with server-side rendering, whereas oninput= does not.