Unable to remove null from observable array - rxjs

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.

Related

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

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
})
)

subscribing to multiple observable while keeping reference to function parameters

I have an input of a string array for Enums I want to recieve from server:
enumList = ['somethin','branch','country', 'serviceType', 'thatandthis'];
I then have a generic http-service method that takes an enumList string as a parameter and returns an HttpClient observable for that Enum service:
this.webApi.getEnumByName('somethin','en').subscribe((res)=>{/*do something*/})
this.webApi.getEnumByName('branch','en').subscribe((res)=>{/*do something*/})...
I'm than combining the two into a loop
for (const item of this.enumList) {
this.webApi.getEnumByName(item).subscribe((res: any) => {
this.enums[item] = res;
});
}
But this is not good...
I want the a subscription that completes only once when all subscriptions has resolved, while keeping a reference to the associated item string
using an array of observables returned from this.webApi.getEnumByName(item), concat or forkJoin won't work because they won't keep refference to the associated string/key/token of the response e.g the string in the enumList.
The end result of these concatinated observables should be:
{
'somethin':{respopnse...},
'branch':{respopnse...},
'country':{respopnse...},
'serviceType':{respopnse...},
'thatandthis':{respopnse...}
}
breaking my head on this will appriciate an rxjs solution
If I understand right your problem, you may want to consider something like this.
First of all you build an Observable with a function like this
function obsFromItem(item) {
return this.webApi.getEnumByName(item).pipe(
tap(res => this.enums[item] = res),
)
}
The above logic says that as soon as getEnumByName notifies its result, than the result is set into this.enums at the right item.
Now that you have a similar function, you can create an array of Observables to be passed into forkJoin like this
arrayOfObs = enumList.map(item => obsFromItem(item))
forkJoin(arrayOfObs).subscribe()
When forkJoin(arrayOfObs) notifies, it means that all the Observables built via obsFromItem have emitted and therefore this.enums should be rightly filled.
forkJoin gives you parallel execution. If you substitute forkJoin with concat you get sequential execution.
In this article you may find some typical patterns of use of Obaservables with http calls.
You can combine several observables together like that:
forkJoin(enumList.reduce<any>((result, key) => {
result[key] = this.webApi.getEnumByName(key,'en');
return result;
}, {})).subscribe(allTogether => {
// allTogether.somethin;
// allTogether.branch;
// ...
});
You can create a function to pass in this.enumList and still getting the same reference
function getResponse(enum){
return forkJoin(....).subscribe(....)
}
or
forkjoin this.enumList with http call list
forkJoin(of(this.enumList), forkJoion(httpcall1,htttpcall2))
.subscribe([enum,responsesArray]=>....)

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.

Get last emitted object from multiple sources

To be more specific, I don't want an array of emitted object. I want the last object emitted by either source.
Currently, it's working like this
.withLatestFrom(subject1, subject2)
.switchMap([val1, val2]) => {...}
What I want is
.withLatestFrom(subject1, subject2)
.switchMap(latestVal) => {...}
To avoid a possible x=y problem. What I have is two subjects. Subject1 and Subject2.
If subject2 has emitted any values, that value will always trump subject1. In my use case once subject 2 has started emitted, this particular module will ignore subject 1. But subject 1 will be used elsewhere.
But I want a way to feed these two subjects as sources and either get the last emitted value, or the last value of subject 2 if it's not null. If subject 2 is null then get the last value from subject 1.
Here is an example that I think does what you are looking for:
const first = Rx.Observable.interval(1000).map(x => `first (${x})`);
const second = Rx.Observable.interval(2000).map(x => `second (${x})`);
Rx.Observable.merge(
first.takeUntil(second),
second
)
.subscribe(
x => { console.log('next: ', x); },
null,
() => { console.log('complete'); }
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.5/Rx.min.js"></script>
The takeUntil will cause the "first" observable to stop emitting values for this subscription the first time the "second" observable emits.

combineAll does not emit on empty array

JSBIN Sample
I have a changeable set of child components (POJO object) that each have its own state stream. Each time a user triggers addChild/removeChild/clearChildren, a new set of children state streams is emitted with #switchMap. So far so good! (And so amazed by RxJS!)
With Rx.Observable.from(arrayOfStateStreams).combineAll() I get a good result as long as the arrayOfStateStreams isn't an empty array.
Since this is a partial state that is combined(Latest) on a higher level, I need to get an empty array emitted or the global state tree will contain old state data that is no longer true!
I can emit some reserved token like ['EMPTY-ARRAY-PLACEHOLDER-TOKEN'], but that's just weird.
A better way would be to always append one last stream into the array so the last index can be considered trash. Still confusing code and state though.
Using [null] is not OK, since we could have a child state of 'null'.
Anyone who can solve this in a good way? Can't this be supported since there should be no other representation of an empty array after #combineAll?
Credits go to github user trxcllnt who provided the following answer:
combineAll won't emit unless the combined Observables emit at least
one value, but you could check to ensure the collection you're
combining is empty or not, and either combine or emit an empty Array:
var arrayOfStreamsStream = Rx.Observable
.of(
[], [
Rx.Observable.of('blah-1'), // component state.
Rx.Observable.of('blah-2'),
Rx.Observable.of('blah-3')
], [], [
Rx.Observable.of('foo-1'),
Rx.Observable.of('qux-2')
]
)
.switchMap(function onMap(coll) {
return coll.length === 0 ?
Observable.of(coll) :
Observable.combineLatest(...coll);
})
.subscribe(function onSubscribe(data) {
console.log('onSubscribe START')
console.dir(data)
console.log('onSubscribe END')
})
This has nothing to do with combineAll. The problem is that Observable.from results in nothing (an empty observable) when passed an empty array.
The only viable solution that I can think of if you have to get a result from an empty array is to return something else in that case.
Ann example to illustrate the problem and a possible solution.
var data = [1, 2, 3, 4, 5];
log('With data: ');
Rx.Observable.from(data)
.subscribe(function (d) { log('data: ' + d); });
// Prints:
// With data:
// data: 1
// data: 2
// data: 3
// data: 4
// data: 5
var data = [];
log('Without data: ');
var nullDataObject = { msg: 'my null data object' };
Rx.Observable.from(data.length == 0 ? [nullDataObject] : data)
.subscribe(function (d) { log('data: ' + d); });
// Prints:
// With data:
// data: [object Object]
Runnable example on jsfiddle.
When consuming this you simply filter away the object representing an empty array where appropriate.
a possible workaround is to just pipe it with startWith();
combineLatest(potentiallyEmptyArray).pipe(
startWith<any>([])
);
Note: Similar issues exist with combineLatest() (the static version) which can be solved using defaultIfEmpty() - which works, but it screws up the typing of the output.
// array of Observables
const animals: Observable<{ species: 'dog' | 'cat' }>[] = [];
// Type '{ species: "dog" | "cat"; }[]' is not assignable to type 'never[]'.
combineLatest(animals).pipe(defaultIfEmpty([]));
In TypeScript you need to either know the type of the object or use <any>[] which means you then lose typing completely.
If you have a concrete type you can use one of these:
defaultIfEmpty<Animal[]>([])
defaultIfEmpty([] as Animal[])
I often don't have a concrete type for the return value of an observable. So I came up with an operator:
export const emptyArrayIfEmpty = () => <T>(observable: Observable<T[]>) =>
observable.pipe(defaultIfEmpty([] as T[]));
Then I can add the following and get out an empty array if animals === [] without losing any typing information:
combineLatest(animals).pipe(emptyArrayIfEmpty());

Resources