Search code examples
javascriptangularrxjs

How to force evaluation of expression in Observer at time Observer is created rather than at time values are emitted


I posted this similar question but didn't receive any answers.(I thought I had an answer but now I'm not so sure, and am still quite confused.

I have a Reactive Form in which it appears the function inside the following subscription evaluates itm["value"].length-1 at the time the observable emits data (and calls the subscription function).

  this.formCtls[controlName] = new FormControl('', {updateOn: 'blur'});
  this.userForm.addControl(controlName, this.formCtls[controlName]);
  this.formCtls[controlName].valueChanges.subscribe(val=>{
    itm["value"][itm["value"].length-1]=val;
    this.renderDataArray();
  });

However, I want the subscription callback function expression itm["value"].length-1 to be evaluated at the time the observable/(FormControl) is created.

For instance, at the time the form control is created, itm["value"].length might only be 2, but at the time the Observable emits data, itm["value"].length may be 6 or 7 or any other number. How can I (programatically) ensure that the value of "2" (or its equivalent) goes into the subscription callback, rather than "6", or "7", or whatever might be the value when the Observable emits?

I've thought of two approaches, and I'm not sure if either is valid:

  1. declare const itmLength = itm["value"].length first, and then construct the callback, i.e.:
const itmLength = itm["value"].length;
this.formCtls[controlName] = new FormControl('', {updateOn: 'blur'});
      this.userForm.addControl(controlName, this.formCtls[controlName]);
      this.formCtls[controlName].valueChanges.subscribe(val=>{
        itm["value"][itmLength-1]=val;
        this.renderDataArray();
      });

But I'm not sure if itmLength contains an actual value (in which case the approach above would work), or rather a pointer to itm["value"].length (in which case the approach above wouldn't work). I thought this approach was working properly, but then I changed my code and encountered problems with "block scope variables", and the only way I could fix that problem was by declaring the const/variable outside the inner loop, and after doing so the code stopped giving me the "2" I was expecting and continued to give me the "6" or "7" that I didn't want. So in general, is this first approach valid? (or is const itmLength just a pointer, in which case itm["value"].length will only be evaluated when the Observable emits)?

A second approach would be to add a pipe into the observable, something like:

this.formCtls[controlName].valueChanges.pipe(
        switchMap(v=>Object.assign({},{value: v}, {length: itm["value"].length}))
        .subscribe(val=>{
        itm["value"][val.length-1]=val.value;
        this.renderDataArray();
      });

I'm not sure if the second approach provides any benefit, or whether itm["value"].length would only be evaluated only when the valueChanges Observable emits, rather than at the time the formControl callback is created.

Solution

Here is the final answer (accounting for deprecation of resultsSelector) based on the excellent solution from @Fan Cheung:


of(itm["value"].length).
   pipe(switchMap(length=>this.formCtls[ctlName].valueChanges.
      pipe(map(newVal => [newVal,length]))
      )).
   subscribe(([newVal,length])=> . . .

Solution

  • You can compose itm["value"].length into the stream which is integer not pointer, hence it is fixed at creation time. of operator will only emit once then switch to valueChanges stream

    of(itm["value"].length)
    .pipe(switchmap(length=>this.formCtls[controlName].valueChanges,
    // result seletor
    value=>[length,value]))
    .subscribe(([length,value])=>{
           ....... do you stuff
          });