Home > OS >  How to return two modified Observable<[]> from a single Observable<[]>
How to return two modified Observable<[]> from a single Observable<[]>

Time:10-27

I have an Observable<Person[]> where the Person interface looks like this:

export interface ToolfaceData {
    age: number;
    name: string;
}

I want to create two Observable<Person[]>s where one adds 2 to the age of all Persons in the array, and the other subtracts 2 from the age of all Persons in the array.

So if this is the data streamed to me: of (Person{age:2, name: Anne}, Person{age:5, name: Bob}), then I'd create two Observable<Person[]>:
of (Person{age:4, name: Anne}, Person{age:7, name: Bob}) and of (Person{age:0, name: Anne}, Person{age:3, name: Bob})

How exactly do I go about doing this?

What I've tried:

  1. I've tried tap and found that when I create just one of the two observables, it works, but when I create both, both are the original Observable (I'm not sure why this happens, but I guess it's not my main question here at the moment).
  2. I've tried map but can't find a way to do it without modifying the original data while creating the first observable, which creates issues when I try to create the second observable.

CodePudding user response:

I am not sure about your intention, but...

tap is used only for side effects, actually it does not have impact on the observable pipe at all. It is used e.g. for debugging or logging.

map is used to remap the passed value to another one. E.g. you can add age to Person like .pipe(map(person => Person{age: person 2, name: person.name}))

mergeMap is used to remap passed value to new observable whose values will be passed to the output stream. It is used e.g. for async API calls.

So probably what you are trying to do could be this:

source$.pipe(
  mergeMap(person => concat( // concat two observables
    of(Person{age: person   2, name: person.name}), // .pipe(...),
    of(Person{age: person - 2, name: person.name}) // .pipe(...)
  ))
)

There are alternatives to concat, like merge or race. It depends on your desired behaviour.

CodePudding user response:

You can assign an initial Observable data stream to a new observable variable, modify the data with pipeable operators and subscribe to the value separately without modifying the original source data.

I used the map function to add 2 to the age.

const personObservable$ = of(
  {
    age: 2,
    name: 'Anne',
  },
  {
    age: 5,
    name: 'Bob',
  }
);

personObservable$.subscribe(person => console.log('Original', person));

const personAgePlus$ = personObservable$
  .pipe(
    map(({age, name}) => {
      age = age   2;
      return {age, name};
    })
  ).subscribe(person => console.log('Age  2', person));


const personAgeMinus$ = personObservable$
  .pipe(
    map(({age, name}) => {
      age = age - 2;
      return {age, name};
    })
  ).subscribe(person => console.log('Age -2', person));


CodePudding user response:

If you're looking to create one (or more) observables from an existing observable, you can simply define it to start with the original, then pipe() the emission to your desired value.

So if your "original" value looks like this:

people$ = this.service.getPeople();

You can define your other observables like this:

olderPeople$ = this.people$.pipe(
  map(people => people.map(p => ({...p, age: p.age   2})))
);

youngerPeople$ = this.people$.pipe(
  map(people => people.map(p => ({...p, age: p.age - 2})))
);

Here's a working StackBlitz demo.


Note: if multiple observables come from the same source, you can apply shareReplay to the original to prevent the underlying logic (in this case call to getPeople()) from being executed once for each subscription:

people$ = this.service.getPeople().pipe(shareReplay());
  • Related