custom Rxjs filterNullish operator that takes array of values and changes each return type - rxjs

I have this custom operator that filters out nullish values (undefined and null) and also changes the return type to indicate that undefined or null is no longer a possible value.
Now I would like to expand this operator to not only take a single typed value but also support an array of typed values and filter if any of the arrays values are nullish and use the correct return type, such that it can be used in merge operator chain like combineLatest
export function filterNullish<T>(): UnaryFunction<Observable<T | null | undefined>, Observable<T>> {
return pipe(filter(notNullish));
}
function notNullish<T>(value?: T | null | undefined): value is T {
return value != null && value != undefined;
}
Current usage, this works:
obs$ = this.store.select(selectSomething).pipe(
// type of something is possibly null
filterNullish(),
// type of something is not null anymore
map((something) => this.createOtherThing(something))
);
Desired usage:
combineLatest([this.something$, this.otherthing$]).pipe(
filterNullish(),
// I want typescript to know EACH of the arrays elements types are not nullish anymore
tap(([something, otherthing]) => {
// do stuff knowing something and otherthing can not be nullish anymore
})
)

fixed by constructing new array by filterNullish per element and passing tuple type:
export const filterNullishAll: <T>() => OperatorFunction<Array<any>, T> = <
T
>() =>
pipe(
map((collection: any[]): T | null =>
collection.every(notNullish) ? ([...collection] as T) : null
),
filter(notNullish)
);
Usage:
combineLatest([this.something$, this.otherthing$]).pipe(
filterNullishAll<[SomethingType, OtherThingType]>(),
tap(([something, otherthing]) => {
// do stuff knowing something and otherthing can not be nullish anymore
})
)

Related

Is it possible to remove warning from Data validation while using criteria - list from a range?

I have set a data validation as a list from a range but when I enter a text that does not match the list, I am getting a red warning in the cell.
My question is: How to use both methods, but avoiding the red warning? How to make datavalidation accept both which is available in range list, and that which is not available in range list, but also keep the dropdown?
Using onEdit(e), I have a script that will compare the value input to the data validation rule and if its not in the list, the error may be displayed momentarily but the value will be replaced with "" which is a valid input.
My data validation is Sheet1!C5:D5 merged cell for a range, Sheet!C7:D7 for a list.
DataValidation.getCriteriaValues() returns an array of objects the first element of that array is an array of the values displayed in the dropdown for a list or a range object.
function onEdit(e) {
try {
if( e.range.getSheet().getName() === "Sheet1") {
if( ( e.range.getA1Notation() === "C5" ) || ( e.range.getA1Notation() === "C7" ) ) {
let valid = e.range.getDataValidation();
if( valid != null ) {
let criteria = valid.getCriteriaValues();
let values = criteria[0];
if( valid.getCriteriaType() === SpreadsheetApp.DataValidationCriteria.VALUE_IN_RANGE ) {
values = values.getValues().flat();
}
if( values.indexOf(e.value) < 0 ) e.range.setValue("");
}
}
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}
Reference
Range.getDataValidation()
DataValidation.getCriteriaValues()

Unable to remove null from observable array

Even though I've defined a type guard filter, I can't seem to get rid of my null observables. I defined my type guard:
export const hasValue = <T>(value: T): value is NonNullable<T> => value !== null && value !== undefined
and then my methods that want to filter like so:
#validateDocument(document: IDocument): Observable<AcmsDocumentBad | null> { ... }
validateDocuments(documents: IDocument[]): Observable<AcmsDocumentBad[]> {
const observables = documents
.filter(x => x.approvedSor === 'ACMS')
.map(this.#validateDocument)
return forkJoin(observables)
.pipe(filter(hasValue))
}
I had expected that uses the pipe like that would result in Observable<AcmsDocumentBad[]> as the type, but it's still showing as Observable<(AcmsDocumentBad | null)[]>
forkJoin(Array<Observable<AcmsDocumentBad | null>>) returns an Observable<Array<AcmsDocumentBad | null>>, which emits an array with all the results just once, when all of the inner observables have completed.
If you do .pipe(filter(hasValue)) on this, the result will be the same, because the observable never emits nulls, it will emit just one Array, guaranteed. The inner values of that array could be null though.
You probably want to just do .pipe(map(array => array.filter(hasValue)))
Or if you don't fancy array methods, .pipe(switchAll(), filter(hasValue), toArray()) (i.e. flattening the Observable<Array<T>> to Observable<T>, filtering, then recreating the array with the values filtered.

RxJS observable which emits both previous and current value starting from first emission

I have a BehaviorSubject which emits JavaScript objects periodically. I want to construct another observable which will emit both previous and current values of the underlying observable in order to compare two objects and determine the delta.
The pairwise() or bufferCount(2, 1) operators are looking like a good fit, but they start emitting only after buffer is filled, but I require this observable to start emitting from the first event of the underlying observable.
subject.someBufferingOperator()
.subscribe([previousValue, currentValue] => {
/** Do something */
})
;
On first emission the previousValue could be just null.
Is there some built-in operators that I can use to achieve the desired result?
Actually, it was as easy as pairing pairwise() with startWith() operators:
subject
.startWith(null) // emitting first empty value to fill-in the buffer
.pairwise()
.subscribe([previousValue, currentValue] => {
if (null === previousValue) {
console.log('Probably first emission...');
}
})
;
Here's a simple operator:
function withPreviousItem<T>(): OperatorFunction<
T,
{
previous?: T;
current: T;
}
> {
return pipe(
startWith(undefined),
pairwise(),
map(([previous, current]) => ({
previous,
current: current!
}))
);
}
The nice thing about this is that the result has meaningful property names and correct types:
previous is T | undefined
current is T (not T | null)
Stackblitz example
Here's the snippet for rxjs 6+
subject
.pipe(
startWith(undefined),
pairwise()
)
.subscribe(([previousValue, currentValue]) => {
/** Do something */
});
The value in startWith() should be undefined because there is no value. Typically null is defined as "we have a value and this value is empty".
scan (RX equivalent of a reduce) is an option here:
subject
.scan((accumulator, currentValue) => {
const previousValue = ...accumulator.slice(-1);
return [previousValue, currentValue];
}, [null]) // emitting first empty value to fill-in the buffer
.subscribe(([previousValue, currentValue]) => {
// ...
});
This can be extended to a more general case when you want to look at more than two items.

Entity Framework: Any or All - Unable to create a constant value of type 'System.Collections.Generic.List`1'

I am trying to do something like this:
from t in ent.myEntities
where SelectedProperties == null || SelectedProperties.Any(le => le == t.Entity)
select t
basically trying to cover 2 cases. accepting an empty list, should return all entities, or filter on the list if it is supplied.
above actually does work when i supply the list, however in the case when it is null i get:
Unable to create a constant value of type
'System.Collections.Generic.List`1'. Only primitive types ('such as
Int32, String, and Guid') are supported in this context
also tried using this with a string array:
where arr == null || arr.Contains(t.Entity)
is it possible to have such a condition without having to build a predicate (which is a bigger effort)?
You might want to try using the list in a simpler way:
where SelectedProperties == null || SelectedProperties.Contains(t.Entity)
It may well not work, but it's worth a try. Otherwise, if this is really your whole query, I'd just write it as:
var query = SelectedProperties == null
? ent.myEntities
: ent.myEntities.Where(t => SelectedProperties.Contains(t.Entity));
EDIT: Okay, if you have to use Any, and have lots of these to compose, you can do it like this:
var query = ent.myEntities;
if (SelectedProperties != null)
{
query = query.Where(t => SelectedProperties.Any(x => x == t.Entity));
}
if (SomethingElse)
{
query = query.Where(...);
}
// etc
I'm using EF5, something like this will fix the issue:
ent.myEntities.ToList().Where(t => SelectedProperties == null || SelectedProperties.Contains(t.Entity));

Why is the sum of an empty set null?

If I do:
int updateGamePlays = db.tblArcadeGames.Where(c => c.ParentGameID == GameID).Sum(c => c.Plays);
If no records are returned in this query it throws:
System.InvalidOperationException: The null value cannot be assigned to
a member with type System.Int32 which is a non-nullable value type.
The only way to get it to return 0 is by doing:
int updateGamePlays = db.tblArcadeGames.Where(c => c.ParentGameID == GameID).Sum(c => (int?)c.Plays) ?? 0;
In the database c.Plays is a non-nullable int.
In set theory the sum of an empty set should equal 0 (ref). How comes in Linq-to-SQL did they decide to make it return null?
According to a source at Microsoft, Sum() on an empty set is null because of the way it works in SQL:
when the table is empty i´m getting this exception: InvalidOperationException
In SQL, Sum() aggregate operator returns null for an empty set. So this is as designed.
Another alternative is to add a 0 to the set to make sure there's always at least one value.
int updateGamePlays = db.tblArcadeGames.Where(c => c.ParentGameID == GameID)
.Select(c => c.Plays)
.Concat(new [] { 0 })
.Sum();
You can use the more general Aggregate method with a seed of zero:
int updateGamePlays = db.tblArcadeGames
.Where(c => c.ParentGameID == GameID)
.Aggregate(0, (a, c) => a + c.Plays);
This does not require using nullable types.

Resources