I am struggling to figure out how to implement data down, actions up in a glimmer component hierarchy (using Ember Octane, v3.15).
I have a parent component with a list of items. When the user clicks on a button within the Parent
component, I want to populate an Editor
component with the data from the relevant item; when the user clicks "Save" within the Editor
component, populate the changes back to the parent. Here's what happens instead:
How can I make the text box be populated with "Hello", and have changes persisted back to the list above when I click "Save"?
Code
<ul>
<li> <button >Edit</button></li>
</ul>
<Editor @currentModel= @save= />
// app/components/parent.js
import Component from '@glimmer/component';
export default class ParentComponent extends Component {
@tracked models = [
{ id: 1, text: 'Hello'},
{ id: 2, text: 'World'}
]
@tracked currentModel = null;
@action
edit(model) {
this.currentModel = model;
}
@action
save(model) {
// persist data
this.models = models.map( (m) => m.id == model.id ? model : m )
}
}
<small>Editing ID: </small>
<Input @value= />
<button >Save</button>
// app/components/editor.hbs
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
export default class EditorComponent extends Component {
@tracked text;
@tracked id;
constructor() {
super(...arguments)
if (this.args.currentModel) {
this.text = this.args.currentModel.text;
this.id = this.args.currentModel.id;
}
}
@action
save() {
// persist the updated model back to the parent
this.args.save({ id: this.id, text: this.text })
}
}
Rationale/Problem
I decided to implement Editor
as a stateful component, because that seemed like the most idiomatic way to get form data out of the <Input />
component. I set the initial state using args
. Since this.currentModel
is @tracked
in ParentComponent
and I would expect re-assignment of that property to update the @currentModel
argument passed to Editor
.
Indeed that seems to be the case, since clicking "Edit" next to one of the items in ParentComponent
makes <small>Editing ID: </small>
appear. However, neither the value of the <Input />
element nor the id
are populated.
I understand that this.text
and this.id
are not being updated because the constructor
of EditorComponent
is not being re-run when currentModel
changes in the parent... but I'm stuck on what to do instead.
What I've tried
As I was trying to figure this out, I came across this example (code), which has pretty much the same interaction between BlogAuthorComponent
(hbs) and BlogAuthorEditComponent
(hbs, js). Their solution, as applied to my problem, would be to write EditorComponent like this:
<small>Editing ID: </small>
<Input @value= />
<button >Save</button>
// app/components/editor.hbs
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
export default class EditorComponent extends Component {
get isEditing() {
return !!this.args.currentModel
}
@action
save() {
// persist the updated model back to the parent
this.args.save({ id: this.id, text: this.text })
}
}
It works! But I don't like this solution, for a few reasons: - Modifying a property of something passed to the child component as an arg
seems... spooky... I'm honestly not sure why it works at all (since while ParentComponent#models
is @tracked
, I wouldn't expect properties of POJOs within that array to be followed...) - This updates the text in ParentComponent
as you type which, while neat, isn't what I want---I want the changes to be persisted only when the user clicks "Save" (which in this case does nothing) - In my real app, when the user is not "editing" an existing item, I'd like the form to be an "Add Item" form, where clicking the "Save" button adds a new item. I'm not sure how to do this without duplicating the form and/or doing some hairly logic as to what goes in <Input @value
...
I also came across this question, but it seems to refer to an old version of glimmer.
Thank you for reading this far---I would appreciate any advice!
Aucun commentaire:
Enregistrer un commentaire