Say you have a simple BehaviorSubject
this.countryOfBirth$ = new BehaviorSubject<CountryEnum>(null);
get countryOfBirth() {
return this.countryOfBirth$.value;
};
set countryOfBirth(val: CountryEnum) {
this.countryOfBirth$.next(val);
};
So doing instance.countryOfBirth
returns the last value and instance.countryOfBirth = CountryEnum.US
pushes the new value to the Subject.
The problem I'm having is that I pass this subject instance to a custom model-driven form module which by default would wrongly pass a string instead of an instance of my CountryEnum
class.
I can fix this in the forms module but I would like to keep that as decoupled as possible from any app-specific logic so it would make more sense to implement the fix in the BehaviorSubject instance itself.
My question is: is there hook or any other way to apply some changes to each and every "next" value before it triggers its subscribers? In other words, after my code does
instance.countryOfBirth = CountryEnum.US;
Before any of the subscribers are triggered I would like to check whether the value is a string (ex: US) and if so - I would like to get the corresponding CountryEnum instance and pass that to the "next" call instead of the original "US" string.
In code it would look something like
this.countryOfBirth$.onBeforeNext((val) => {
if (typeof val == "string") {
return CountryEnum.getInstance(val);
} else if (val instanceof CountryEnum) {
return val;
} else {
throw new Error("Unsupported value type");
}
});
but obviously onBeforeNext
doesn't exist and I can't seem to find anything in the dox that would do what I want.
Your help would be much appreciated!
Since apparently there is no readily available method to do what I needed this is how I implemented my solution using the approach that @estus mentioned in the comments:
// BehaviorSubjectWithValidation.ts
import { BehaviorSubject } from "rxjs/BehaviorSubject";
export class BehaviorSubjectWithValidation<T> extends BehaviorSubject<T> {
private _validator: (val: T) => T;
constructor(defaultValue: T, validator: (val: T) => T) {
super(defaultValue);
this._validator = validator;
}
next(value: T): void {
if (this._validator) {
value = this._validator(value);
}
super.next(value);
}
}
Then in my CountryEnum
class I added the following method
public static parse: (val: any) => CountryEnum = (val: any) => {
if (val) {
if (typeof val === "string") {
return CountryEnum.getInstance(val);
} else if (val instanceof CountryEnum) {
return val;
} else {
throw new Error("Unsupported CountryEnum value");
}
} else {
throw new Error("Invalid country");
}
}
And then I use it in the following way in my main app's logic:
this.countryOfBirth$ =
new BehaviorSubjectWithValidation<CountryEnum>(null, CountryEnum.parse);
So now whatever part of my code adds a new value to this.countryOfBirth$
it will always going to filter it through CountryEnum.parse
.
Hope this helps someone!