I've often used this kind of computed properties where the setter simply returns the new value :
@computed('args.myValue')
get myValue() {
return this.args.myValue;
}
set myValue(newValue) {
return newValue; // <==== this is no longer valid with native setter
}
This does few things :
args.myValue
<Input @value={{this.myValue}} />
)args.myValue
changesThe problem comes with native setters which can't return
any value.
Notice I could probably find a "hackish" solution but I'd like to have code that follows new EmberJS conventions in order to avoid painfull later updates.
Manual caching
@tracked _myValue = null;
get myValue() {
return this._myValue || this.args.myValue;
}
set myValue(newValue) {
this._myValue = newValue;
}
This does not work because _myValue
is always set after the first myValue=(newValue)
.
In order to make it work, there should be some kind of observer which resets it to null
on args.myValue
change.
Sadly, observers are no longer part of EmberJS with native classes.
{{unbound}}
helper
<Input @value={{unbound this.myValue}} />
As expected, it does not work because it just doesn't update myValue
.
{{unbound}}
helper combined with event.target.value
handling
<Input @value={{unbound this.myValue}} {{on "keyup" this.keyPressed}} />
get myValue() {
return this.args.myValue;
}
@action keyPressed(event) {
this.doStuffThatWillUpdateAtSomeTimeMyValue(event.target.value);
}
But the Input
is still not updated when the args.myValue
changes.
Here is a more concrete use example :
Component
// app/components/my-component.js
export default class MyComponent extends Component {
@computed('args.projectName')
get projectName() {
return this.args.projectName;
}
set projectName(newValue) {
return newValue; // <==== this is no longer valid with native setter
}
@action
searchProjects() {
/* event key stuff omitted */
const query = this.projectName;
this.args.queryProjects(query);
}
}
{{! app/components/my-component.hbs }}
<Input @value={{this.projectName}} {{on "keyup" this.searchProjects}} />
Controller
// app/controllers/index.js
export default class IndexController extends Controller {
get entry() {
return this.model.entry;
}
get entryProjectName() {
return this.entry.get('project.name');
}
@tracked queriedProjects = null;
@action queryProjects(query) {
this.store.query('project', { filter: { query: query } })
.then((projects) => this.queriedProjects = projects);
}
@action setEntryProject(project) {
this.entry.project = project;
}
}
{{! app/templates/index.hbs }}
<MyComponent
@projectName={{this.entryProjectName}}
@searchProjects={{this.queryProjects}} />
When the queriedProjects
are set in the controller, the component displays them.
When one of those search results is clicked, the controller updates the setEntryProject
is called.
According to this Ember.js discussion :
Net, my own view here is that for exactly this reason, it’s often better to use a regular
<input>
instead of the<Input>
component, and to wire up your own event listeners. That will make you responsible to set the item.quantity value in the action, but it also eliminates that last problem of having two different ways of setting the same value, and it also gives you a chance to do other things with the event handling.
I found a solution for this problem by using standard <input>
, which seems to be the "right way" to solve it (I'll really appreciate any comment that tells me a better way) :
{{! app/components/my-component.hbs }}
<input value={{this.projectName}} {{on "keyup" this.searchProjects}} />
// app/components/my-component.js
@action
searchProjects(event) {
/* event key stuff omitted */
const query = event.target.value;
this.args.queryProjects(query);
}
If I needed to keep the input value as a property, I could have done this :
{{! app/components/my-component.hbs }}
<input value={{this.projectName}}
{{on "input" this.setProjectQuery}}
{{on "keyup" this.searchProjects}} />
// app/components/my-component.js
@action setProjectQuery(event) {
this._projectQuery = event.target.value;
}
@action
searchProjects( {
/* event key stuff omitted */
const query = this._projectQuery;
this.args.queryProjects(query);
}
EDIT
Notice the following solution has one downside : it does not provide a simple way to reset the input value to the this.projectName
when it does not change, for example after a focusout.
In order to fix this, I've added some code :
{{! app/components/my-component.hbs }}
<input value={{or this.currentInputValue this.projectName}}
{{on "focusin" this.setCurrentInputValue}}
{{on "focusout" this.clearCurrentInputValue}}
{{on "input" this.setProjectQuery}}
{{on "keyup" this.searchProjects}} />
// app/components/my-component.js
// previous code omitted
@tracked currentInputValue = null;
@action setCurrentInputValue() {
this.currentInputValue = this.projectName;
}
@action clearCurrentInputValue() {
this.currentInputValue = null;
}