-
Notifications
You must be signed in to change notification settings - Fork 9.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design proposal: Bind get/set/after modifiers #39837
Comments
Perhaps |
It'll also make it easier to understand that it's a handler, but will break the "get/set" combination, which is also easy to remember. Choosing between easy to remember and intuitive / help me understand what's going on here - I'd chose the latter. So, |
|
I somewhat dislike |
Does |
|
@SteveSandersonMS overall a very solid proposal, I like the direction this goes in an the fact that people can reuse our custom bind "reconciliation" logic. |
I agree. I don't really see
I have to actually implement it to get to 100% solid certainty, but from what I anticipate, no. My intent is that the |
|
I love this proposal. Not kidding, huge improvement. Typical of @SteveSandersonMS . |
|
Will I be able to use :after (and others) with components? <MyComponent @bind-SomeValue="_value" @bind-SomeValue:after="HandleValue" /> |
Yes |
|
Really like this proposal. |
|
:after is nice! |
|
Nice! How about using bind:before instead of bind:set? I did find it a bit missleading |
|
@vgb1993 We don't think that will be such a common requirement, and for people who do need that, it's important that they use |
Summary
Provides ways of customizing how Blazor's
@bindreads and writes a value, solving several common problems at once.Motivation and goals
After thinking through the "multiple event handlers" proposal (#39815), I've started to think the real problem we should solve is quite different. That is:
@bind(see below).@bindtoday:A. No safe way to run async logic after a binding updates (#22394). This is a basic scenario even beginners will hit early.
B. The magic consistency mechanism built into
@bind(essential for Blazor Server apps) can't be used if you're implementing avalue/onchangepair manually (#17281 #38520). This is a potential severe gotcha you may not notice until in production.C. When building a component with a
Value/ValueChangedpair, there are two ways to do it, but neither are safe:@value/@onchangepair manually, you lose consistency guarantees (can lose keystrokes on Server)@bindto a property with aget/setpair, you have to discard theTaskreturned byValueChanged.InvokeAsyncIn scope
Value/ValueChangedpairThese two directly address A and C above. If these two are done, I think B is solved implicitly because there's no longer any reason to want to do a manual
value/onchangepair. If the behavior you want is conceptually a two-way binding, then@bindwould now always have as much power as you need.Also, I'm keen to do this in an idiomatically Blazory way. For example, there should not be some kind of new
BindableValuethat you have to instantiate at runtime. Instead, typical components should be built only with basic C# types and patterns (e.g.,stringandFunc<string, Task>).Out of scope
@bindsafely (that is, in a way that won't drop keystrokes in async update cases like Blazor Server), I don't think we can realistically stop people from using extra powers to do something misguided.Proposed solution
There are really two distinct cases to deal with. I've considered extensively designs that would solve both using only a single concept, but didn't feel that any of them led to enough elegance or convenience. So in the end I'm proposing two different but closely-related new mechanisms.
bind:after
To handle the "run async logic after binding", consider a
@bind:afterdirective attribute:I really like
@bind:afterbecause it's so basic. Any beginner can understand this. Even just seeing its name in autocompletion is probably enough to work out how to use it. This solves problem A in a super direct way.It's also very good for guiding people not to break the safety mechanism for not losing keystrokes in Blazor Server, since your custom logic doesn't run until we've already assigned the new value to
searchTextsynchronously - all existing guarantees remain. Presumably we'd invokePerformSearchbefore the initial re-render, and as usual, have it trigger a further re-render asynchronously. This would happen automatically if the Razor compiler generates an event handler that does the assignment and then returns the task given byEventCallback.Factory.Create(yourBindAfterExpression).InvokeAsync().bind:get and bind:set
This one's less obvious, but for components that take a
Value/ValueChangedpair, you'd be able to take the scary code you have to write today:... and simplify it to this:
With benefits:
value=sideValueChanged)The behavior of course is that you're controlling parts of the code that
@bindgenerates on both the reading and writing sides. The difference between@bind:setand@bind:afteris that@bind:afterdoes the value writing for you before calling your code, whereas@bind:setjust calls your code (and passes the new value, having gone through@bind's built-in parsing logic).Questions you may have:
@bind:get+@bind:setand not just@bind+@bind:set?<input @bind="@val" @bind:set="@MyMethod" />often, it creates confusion:@bind:setis what makes it a two-way binding, and that you could make it one-way by removing that. Whereas in fact that would be wrong (you'd still have a two-way binding, just one that behaves differently now).<input value="@val" @bind:set="@MyMethod />, and it almost is, but not quite because the formatting logic would differ. Much better not to create the ambiguity and have one correct solution.@bind:getand@bind:setmust always be used as a pair - you can't just have one of them and not the other (nor can you have them with@bind). So none of the weird cases will arise.@bind:setto achieve (in effect)@bind:after, and hence we don't need@bind:after?@bind:setto a method that writes to your field and then runs your async logic. However, this is far less obvious for newcomers, and is less convenient in common cases. And it invites mistakes: if you do something async before setting the field, the UI will temporarily revert and generally behave badly. So it's valuable to have@bind:afterfor convenience and to guide correct usage. We can regard@bind:get/@bind:setas a more advanced case mainly for people implementing bindable components, as it gives them a really clean and safe solution, and such developers are advanced enough to understand that they just shouldn't do async work before callingValueChanged.<input @bind:get="@value" @bind:set="@MyMethod" @bind:after="@DoStuff" />?MyMethodbefore callingDoStuff, since "after" feels like it means "after all the work involved in calling set". It's an edge case but I can't think of any problems this will cause nor any major increase in implementation cost.@bindmodifiers like@bind:eventand@bind:formatwork with this?value/onchangepairs.Risks / unknowns
Depends on the exact design chosen.
In the above design, it's increasing the conceptual surface area of
@binda bit and introducing new rules about which combinations of modifiers can be used together. I don't think this is a killer problem because the compiler can enforce all this cleanly. However it is potentially more for people to learn. Mitigation: only tell most people about@bind:after, as that's the most widely needed and is super easy to understand.Examples
See above
The text was updated successfully, but these errors were encountered: