Closed maonaoda closed 9 months ago
BindableReactiveProperty
provides ErrorsChanged
event.
This is an event for XAML Binding, but can also be subscribed to manually.
If you want to operate with Rx, you can convert this as a FromEvent
.
@neuecc Hi, I am using Maui,
Is that be subscribed manually like this?
Password = new BindableReactiveProperty<string>(string.Empty).EnableValidation(() => Password);
PasswordHasError =
Observable.EveryValueChanged(Password, x => x.HasErrors)
.ToBindableReactiveProperty();
PasswordErrorMessage =
Observable.EveryValueChanged(Password, x => Password.GetErrors(null).OfType<ValidationResult>()?.FirstOrDefault()?.ErrorMessage)
.ToBindableReactiveProperty();
ReactiveProperty`s using:
Password = new ReactiveProperty<string>(string.Empty).SetValidateAttribute(() => Password).AddTo(Disposables);
PasswordHasError = Password.ObserveHasErrors.ToReactiveProperty().AddTo(Disposables);
PasswordErrorMessage = Password.ObserveValidationErrorMessage().ToReactiveProperty().AddTo(Disposables);
First, R3 is not intended to port the full functionality of ReactiveProperty (more like ReactivePropertySlim, if anything). If that works, then it's good.
I added this method for myself. Thanks for your guidance.
public static class BindableReactivePropertyExtensions
{
public static Observable<bool> ObserveHasErrors<T>(this BindableReactiveProperty<T> source)
{
return Observable.EveryValueChanged(source, x => x.HasErrors);
}
public static Observable<string?> ObserveValidationErrorMessage<T>(this BindableReactiveProperty<T> source)
{
return Observable.EveryValueChanged(source, x => x.GetErrors(null)?.OfType<ValidationResult>()?.FirstOrDefault()?.ErrorMessage);
}
}
The above code is constantly executing, and I changed it to this (FromEvent
).
There are slight differences of the actual action with ReactiveProperty, but it does not affect the final effect.
Although BindableReactivePropertys
HasErrorsor
GetErrors` can be used directly in Xaml, other processing is required in our app.
PasswordHasError = Password.ErrorsChangedAsObservable()
.CombineLatest(PasswordUnfocused, (_, y) => (Password.HasErrors && y))
.ToBindableReactiveProperty();
PasswordErrorMessage = Password.ErrorsChangedAsObservable().Select(_ => Password.GetErrorMessage())
.ToBindableReactiveProperty();
public static Observable<(object? sender, DataErrorsChangedEventArgs e)> ErrorsChangedAsObservable<T>(this BindableReactiveProperty<T> source)
{
return Observable.FromEventHandler<DataErrorsChangedEventArgs>(handler => source.ErrorsChanged += handler,
handler => source.ErrorsChanged -= handler).AsObservable();
}
public static string? GetErrorMessage<T>(this BindableReactiveProperty<T> source)
{
return source.GetErrors(null)?.OfType<ValidationResult>()?.FirstOrDefault()?.ErrorMessage;
}
Since R3 v1.1.1 released, I changed to ObservePropertyChanged
to migrate form ReactiveProperty,
Because DataErrorsChangedEvent just is ErrorsChangedEvent.
public static Observable<bool> ObserveHasErrors<T>(this BindableReactiveProperty<T> source)
{
return source.ObservePropertyChanged(x => x.Value)
.Select(_ => source.HasErrors);
}
public static Observable<string?> ObserveValidationErrorMessage<T>(this BindableReactiveProperty<T> source)
{
return source.ObservePropertyChanged(x => x.Value)
.Select(_ => source.GetErrorMessage());
}
private static string? GetErrorMessage<T>(this BindableReactiveProperty<T> source)
{
return source.GetErrors(null)?.OfType<ValidationResult>()?.FirstOrDefault()?.ErrorMessage;
}
May be v1.1.2 fixed Errors`s disappeance, that I can use FromEventHandler
May be v1.1.2 fixed Errors`s disappeance, that I can use FromEventHandler again ?
Thats right, use FromEventHandler instead of ObservePropertyChanged, since that ObservePropertyChanged will handel every value changed.
@neuecc
One more thing.
Because CombineLatest has the check of Value existence, when I use FromEventHandler like ↓,
Unless calling Password.ForceNotify();
manually after,the using of FromEventHandler will not have value at the first time.
and CombineLatest will not be excuted for the first time.
PasswordHasError = Password
//.ErrorsChangedAsObservable()
.ObserveHasErrors()
.CombineLatest(PasswordUnfocused, (_, y) => (Password.HasErrors && y))
.ToBindableReactiveProperty();
you ca use Prepend for initial value
Prepend is OK.
public static Observable<bool> ObserveHasErrors<T>(this BindableReactiveProperty<T> source)
{
return source
.ErrorsChangedAsObservable()
//.ObservePropertyChanged(x => x.Value)
.Select(_ => source.HasErrors)
.Prepend(true);
}
public static Observable<string?> ObserveValidationErrorMessage<T>(this BindableReactiveProperty<T> source, string defaultError)
{
return source
.ErrorsChangedAsObservable()
//.ObservePropertyChanged(x => x.Value)
.Select(_ => source.GetErrorMessage())
.Prepend(defaultError);
}
Prepend is OK.
Take back this statement as .Prepend(defaultError);
won`t set inner errors.
so previouslyHasErrors is false, but i want it is true.
reuse ForceNotify() finally~
I knew that R3 it the next ReactiveProperty,
Since there are no ObserveHasErrors, How do I need to express it using R3? It that Password.ErrorsChanged ?
PasswordHasError = Password.ObserveHasErrors.CombineLatest(PasswordUnfocused, (x, y) => x && y).ToReactiveProperty().AddTo(Disposables);
※There is also ObserveValidationErrorMessage