Skip to content

Two way binding to model#11341

Open
ogoffart wants to merge 9 commits intomasterfrom
olivier/two_way_bindings
Open

Two way binding to model#11341
ogoffart wants to merge 9 commits intomasterfrom
olivier/two_way_bindings

Conversation

@ogoffart
Copy link
Copy Markdown
Member

Closes #2013

@ogoffart ogoffart force-pushed the olivier/two_way_bindings branch from cf14c0c to 8b34b8d Compare April 11, 2026 21:25
@ogoffart ogoffart marked this pull request as ready for review April 14, 2026 16:40
@ogoffart ogoffart requested a review from tronical April 14, 2026 16:41
Copy link
Copy Markdown
Member

@tronical tronical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice series :). Some comments inside. My main criticism is that documentation wise this is poorly documented / advertised :)


The right hand side of the `<=>` must be a reference to a property of the same type,
or a field of the same type within a property of struct type.
Inside a `for` loop, it can also reference the current row, which lets widgets write
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep inside the guide I find it a little odd to refer to widgets and basically let that be the documentation for a key feature. One could argue that it just works as expected, but I'd prefer if a paragraph was dedicated to this feature. When is it used? (When reading from a model and wanting to write changes back after for example the user made changes) How is it used? (Two way binding)

Perhaps this belongs into the model section?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sentence here could maybe be rewritten, but i don't agree we need a full paragraph for it. This is not really a new feature, but rather the lack of a feature that was missing before.

property: NamedReference,
/// If property is a struct, this is the fields.
/// So if you have `foo <=> element.property.baz.xyz`, then `field_access` is `vec!["baz", "xyz"]`
field_access: Vec<SmolStr>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite part of this PR, but I'm inclined to suggest handling this as another enum variant to make the code more readable (instead of the extra ifs after the match)

}
}

struct TwoWayBindingModel<T, ItemTree, Getter, Setter> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may be the wrong name for the type. We used it to implement two-way bindings to model data, but I'd say this is more like a two-way binding with custom setter/getter or property with custom setter/getter.

However, until we have a second user we could also keep this called as this.

Comment thread docs/astro/src/content/docs/guide/language/concepts/reactivity.mdx Outdated
Add a BindingCallable that reads from a closure (registering a model row
dependency) and writes back via another closure on `intercept_set`.
The rtti trait and the C++ Property helper expose it to code generators.
Represent two-way bindings as an enum: the existing form binding to
another property becomes `TwoWayBinding::Property`, and a new
`TwoWayBinding::ModelData` variant binds to a row of the enclosing
`for` model (optionally through struct fields).

The resolve pass now walks each element in three phases so that the
model expression of a `for` is resolved with the parent scope before
two-way bindings in its body reference it. Field accesses on the model
row are type-checked against the resolved row type, and a property
declared as `property foo <=> item.field` has its type inferred from
the bound field.

Fixes: #2013
Replace the anonymous `(prop1, prop2, field_access)` tuple with a
`TwoWayBinding` struct, and add an `is_model` discriminator plus a
`resolve_model` helper that walks to the enclosing `for`'s sub-component
and returns the data/index/repeater references used by every code
generator.
For a two-way binding targeting a model row, emit a
`link_two_way_to_model_data` call whose getter reads the row through
the body sub-component's `model_data` property and whose setter writes
back via `model_set_row_data` on the parent's repeater.
Emit a `link_two_way_to_model_data` call for model-row two-way
bindings, mirroring the Rust generator. Extract a
`lower_field_access_chain` helper (also mirroring Rust) and use it from
the non-model `link_two_way_with_map` path and from both accessors
inside `generate_model_two_way_binding`.
Add an rtti entry point and an interpreter-side helper that builds a
(getter, setter) pair walking to the enclosing repeater and reading or
writing the row data through the `model_data` property. Ships tests
that cover plain, length-struct and nested-for bindings across all four
code generators, as well as type inference and the model->UI direction
after a native `set_row_data` call.

Fixes: #2013
Replace the manual `checked: todo.checked` / `toggled => { todo.checked
= self.checked; }` dance with `checked <=> todo.checked`.
Add a test with two nested `for` loops over separate models, a `Base`
component wrapping a `Compo` component with Text, and use-site two-way
bindings that reach both the parent-model row (parent_level == 1) and
the inner-model row (parent_level == 0). Covers UI-to-model propagation
via TouchAreas and model-to-UI propagation via set_row_data, exercised
from Rust, C++, and JS.

Add two_way_binding_model_global_chain.slint exercising a chain through
Glob.global_prop.xxx (struct-field alias) combined with a model-row
binding in a for loop. This relies on the BindingMapper intercept_set
fix.

Enable the full assertion set in two_way_binding_structs2 for
C++ and JS now that BindingMapper propagates writes correctly.
@ogoffart ogoffart force-pushed the olivier/two_way_bindings branch from 6eff896 to d5bc0c4 Compare April 17, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two-way bindings to item in a model

2 participants