1
votes

I have two Observables of the same type that I want to merge into one observable and remove duplicates.

By now I tried basically all rxjs operators but none of them do what I need. See the attached picture: a lot of the rxjs operators change the type of the resulting observable. enter image description here

const a: Observable<Foo[]> = ...
const b: Observable<Foo[]> = ...

These are my two calls to retrieve some data.

Now the two operations that came the closest:

combineLatest(a, b);

This one makes a new array with two indexes and the results of a will go in index 0 and results of b will go into index 1. I cant use it like this.

const c = a.pipe(mergeMap(a1 => b.pipe(map(a2 => [...a1, ...a2]))));

This one nearly got it. The problem here is following: Lets assume a return 2 results (example value: 1 and 2) and b return also 2 results (example value: 2 and 3). So the "2" is a duplicate, which should not appear twice in the resulting observable.

Does anyone know a combination of the operators so I can combine both observables and remove the duplicates?

4

4 Answers

5
votes

Since your observables are http calls, I assume they complete after emitting. you can use forkJoin to combine them:

const combine= forkJoin(a, b).pipe(
    map(([a, b]) => a.concat(b))
);

if your observables do not complete, use combineLatest:

const combine= combineLatest(a, b).pipe(
    map(([a, b]) => a.concat(b))
);
2
votes

If your Observables are not hot, eg. they will not push multiple values and will emit one array and complete, this should work:

combineLatest(a, b).pipe(
  map(([a, b]) => {
    const filtered = a.filter(i => !b.includes(i));
    return [...filtered, b];
  })
)
1
votes

It depends on your exact needs. For example, removing duplicates is way easier if you only mean sequential duplicates (i.e. "AAA" should become "A", but "ABA" is fine). If you want only unique values, I think you need scan with some deduplicating logic. And are they emitting objects, or primitives?

In the simplest case, this will do:

merge(a, b).pipe(distinctUntilChanged())

Or, with removing all duplicates:

const deduplicate = arr => [...new Set(arr)];

merge(a, b).pipe(scan(
  (acc, curr) => deduplicate([...acc, curr]), []
)).subscribe(/* ... */);

(It'll get a little trickier with objects).

Here's a stackblitz: https://stackblitz.com/edit/typescript-a97ag7?file=index.ts&devtoolsheight=100

1
votes

Use zip to achieve this.

import { zip } from 'rxjs';

let age$ = of<number>(27, 25, 29);
let name$ = of<string>('Foo', 'Bar', 'Beer');
let isDev$ = of<boolean>(true, true, false);

zip(age$, name$, isDev$).pipe(
  map(([age, name, isDev]) => ({ age, name, isDev })),
)
.subscribe(x => console.log(x));

stackblitz example: https://stackblitz.com/edit/ued3me?file=index.ts