Home > Software design >  RXJS side-effect execution order
RXJS side-effect execution order

Time:12-01

I have this huge object that I want to be able to set with a one simple function like below

  private setPerson = (partialPerson: Partial<Person>) => {
    this.person$.next({ ...this.person$.value, ...partialPerson });
  };

But when some of the properties changes I want to be able to run a side-effect like this

  personNameEffect$ = this.person$.pipe(
    map(({ name }) => name),
    distinct(),
    tap((name) => {
      // Do some complex calculation 
      this.person$.next({ ...this.person$.value, firstName: name });
    })
  );

  constructor() {
    this.personNameEffect$.subscribe()
  }

This works like I want it to, but the issue is the execution order of these steps, this is what I think is happening:

  1. setPerson function is called with a partial object
  2. The side-effect is ran and setting the values (Keeping old value from call before)
  3. The next inside setPerson is ran and overwriting what the side-effect have changed

How can I fix this execution order? I want the next to set the values and then the side-effect to run. Here is a Stackblitz link to a playground where this can be reproduced in.

CodePudding user response:

Your issue is likely related to the nested next calls and/or convoluted logic flow. Furthermore getting the value out of a BehaviorSubject using value is generally frowned upon as its not reactive.

I have modelled your problem in a more reactive flow ie:

Use the BehaviorSubject only for pushing updates rather than a complete Person. Therefore no longer using it as a store, and avoiding having to update itself:

personUpdates$ = new BehaviorSubject<Partial<Person>>({});

person$ stream accumulates these updates, and effectively stores the 'current' person:

person$ = this.personUpdates$.pipe(
    scan((acc, curr) => ({ ...acc, ...curr }), {
      name: '',
      firstName: '',
      lastName: '',
    } as Person)
  );

Because of the change to the BehaviorSubject, the effect no longer needs to access it's value:

personNameEffect$ = this.person$.pipe(
    map(({name}) => name),
    distinct(),
    tap((name) => {
      console.log('Name changed detected');
      this.personUpdates$.next({ firstName: name });
    })
  );

Finally setName and setLastName are simplified, again due to the the BehaviorSubject only requiring a partial value.

  setName = (name: string) => {
    this.personUpdates$.next({ name });
  };
  setLastName = (lastName: string) => {
    this.personUpdates$.next({ lastName });
  };

Stackblitz: https://stackblitz.com/edit/angular-module-sandbox-kzexrb?file=src/app/app.component.ts,src/app/app.component.html

  • Related