Add protected method to notify InputBase<> of changes to CurrentValue
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
## Background and Motivation
The abstract `InputBase` class exposes the `CurrentValue` property; when set by inheritors of the class, this property can trigger a value change on the base class, by using the default `IEqualityComparer` to detect changes. When its setter detects a value change, it notifies the `EditContext` and the `ValueChanged` callback, ensuring the value change propagates correctly to parent components.
This implementation assumes the only way a value can change is when its default `IEqualityComparer<>` detects a change; which may not be always true, specially for mutable reference types.
The current API doesn't support change detection to mutated reference types, unless they implement `IEquatable<>` to detect their own changes. An example of such types would be `List<>` and most collection types.
Although `InputBase<>` might not be intended for complex controls, simple input controls might still need to use mutable reference types. For example, a simple text box that edits a list of tags (`ISet`) separated by a comma, is currently tricky to implement.
This could be solved by providing inheritors with a way to notify the `InputBase<>` that `CurrentValue` has changed, other than through its setter only.
## Proposed API
A protected method for notifying the `InputBase<>` that `CurrentValue` has changed could be added:
```csharp
public abstract InputBase
{
protected void NotifyCurrentValueChanged()
{
// Propagates changes to `ValueChanged` and `EditContext`.
_ = ValueChanged.InvokeAsync(Value);
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
```
## Usage Examples
The following is a simplified example of how `InputBase<>` could be used to edit a bound `HashSet` value representing a list of tags. In this example, `CurrentValue` property is never set, but its value is mutated.
```csharp
public InputTags : InputBase where TCollection : ICollection
{
// ...
private void AddTag(string tag)
{
CurrentValue.Add(tag);
NotifyCurrentValueChanged();
}
private void RemoveTag(string tag)
{
CurrentValue.Remove(tag);
NotifyCurrentValueChanged();
}
}
```
## Alternative Designs
As an alternative, the method could be async, if really necessary, since it may need to call `ValueChanged.InvokeAsync()`:
```csharp
public abstract InputBase
{
protected async Task NotifyCurrentValueChangedAsync()
{
// Propagates changes to `ValueChanged` and `EditContext`.
await ValueChanged.InvokeAsync(Value);
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
```
## Risks
I don't find any significant risks for this API addition. It should not introduce any breaking change.
This proposed API triggers a value change notification, even when the default `IEqualityComparer<>` would return `false`. Unless any outside code depends on the current exact behavior, no breaking changes would arise from this.
Additionaly, to avoid inheritors calling this proposed method unnecessarily, documentation should be clear about its uses cases, stating that it does not need to be called when `CurrentValue` property setter is used with a new value (example: `CurrentValue = newValue`).
Contributor guide
Research direction
Start by locating the InputBase implementation and reading how CurrentValue, ValueChanged, and EditContext currently handle changes. Compare the proposed synchronous and asynchronous notification APIs, then verify that the chosen behavior covers mutable reference types and clearly documents when inheritors should use it instead of the CurrentValue setter.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100