Home > Back-end >  RxJS Use source observable as source and input value in operator function
RxJS Use source observable as source and input value in operator function

Time:12-16

I have this operator function multiply that I would like to be able to pass the source observable to and a input value within the source. I can achieve this with a switchMap but then I also need to pass the source to the operator which seems a bit redundant.

  source$ = of({
    val: 5,
    factor: 6,
    factorTwo: 3
  });

  multiply(factor: number) {
    return (source: Observable<{ val: number }>) => source.pipe(map(value => value.val * factor))
  }

  simpleUsage$ = this.source$.pipe(this.multiply(1))

  WithFactorOne$ = this.source$.pipe(
    switchMap(x => this.multiply(x.factor)(of(x)))
  )

  WithFactorTwo$ = this.source$.pipe(
    switchMap(x => this.multiply(x.factorTwo)(of(x)))
  )

If I use map the source is passed, but the return type is (source: Observable<{val: number;}>) => Observable<number> rather than Observable<Number> like I get when using switchMap. Is it possible to achieve this without passing the source manually?

  desieredFactor$ = this.source$.pipe(
    map(x => this.multiply(x.factor))
  )

This is a simple example but the general issue is shown. Should I rethink the solution? Can this be accomplished in another way?

CodePudding user response:

You could allow passing a function to multiply, which would then allow you to use any value from the emitted source:

interface Example {
  val: number;
  factor: number;
  factorTwo: number;
}

source$ = of<Example>({
  val: 5,
  factor: 6,
  factorTwo: 3
});

multiply(factor: number | ((source: Example) => number)) {
  return (source: Observable<Example>) =>
    source.pipe(map(value => {
      const f = typeof factor === 'function' ? factor(value) : factor;
      return value.val * f;
    }))
}

// Use with number
simpleUsage$ = this.source$.pipe(this.multiply(1))

// Use with function
WithFactorOne$ = this.source$.pipe(this.multiply(x => x.factor))
WithFactorTwo$ = this.source$.pipe(this.multiply(x => x.factorTwo))

Added note:

If you were to put a return type on the multiply function above, it would read:

function multiply(factor: number | ((source: Example) => number)): OperatorFunction<Example, number>

Since map is an operator function itself, you don't need to manually construct your own by defining a function over a source observable, you can just return the map

Like this:

function multiply(
  factor: number | ((source: Example) => number)
): OperatorFunction<Example, number> {

  return map(value => {
    const f = typeof factor === 'function' ? factor(value) : factor;
    return value.val * f;
  });
  
}

CodePudding user response:

multiply shouldn't concern itself with streams at all, just do the multiplication. It could look like this:

type FactorDefinition = {
    factorValue?: number,
    factorName?: 'factor' | 'factorTwo',
};

const multiply = (
  inputValue,
  { factorName, factorValue }: FactorDefinition,
) => {
  const multiplier = factorName ? inputValue[factorName] : factorValue;

  return inputValue.val * multiplier;
};

const simpleUsage$ = source$.pipe(
  map((value) => multiply(value, { factorValue: 1 }))
);

withFactorOne$ = source$.pipe(
  map((value) => multiply(value, { factorName: 'factor' }))
);

https://stackblitz.com/edit/rxjs-2un7xf?devtoolsheight=60&file=index.ts

  • Related