3

I'm having incredible difficultly trying to remove a property from an observable stream. I have an observable that emits query parameter objects in the url (angular's ActivatedRoute.queryParams). I'm trying to remove a key & value from the emitted values and calling distinctUntilChanged() so that their change does not trigger my observer.

The pluck() allows you to only allow one parameter from a stream through, filter() allows you to filter entire streams,skip() operators allow you to skip entire stream emissions, throttle() to limit the amount of emissions. Though to do the opposite of the pluck() - where you want to allow all but one value of the stream to pass - doesn't exist. The closest you can get to accomplishing it is a map() that creates a new object, removes the property, and returns that new object, but that's glitchy at best and often times does not work.

I've resorted to creating individual subscriptions for all query parameter properties except for the one I'm attempting to ignore. I imagine there has to be a better way to go about this, is there some magical operator that I'm missing or work around? Why is such an obvious operator missing from the reactive library when something like pluck() exists?

edit:

Code:

this.activatedRoute.queryParams
  .map((value) => {
    const newObj = Object.assign({}, value);
    delete newObj['page'];
    return newObj;
  })
  .distinctUntilChanged()
  .skip(1)
  .subscribe(
    (value) => console.log(value)
  );
Nikola Jankovic
  • 947
  • 1
  • 13
  • 23
  • 2
    map() is what you want. What exactly is glitchy or doesn't work about it? Post your code and describe the problem if you want concrete help. – JB Nizet Jan 08 '18 at 20:43
  • @JBNizet edited with code, the stream emits but never executes the success callback/function. – Nikola Jankovic Jan 08 '18 at 20:48
  • 2
    Works as expected here: https://angular-m2mpuv.stackblitz.io (go to test, then click repeatedly on the Test button to change the query params). Note that your distinctUntilChanged is useless, since you haven't specified a comparison function, and all emitted objects will thus be different. – JB Nizet Jan 08 '18 at 21:04
  • 1
    The code is here, BTW: https://stackblitz.com/edit/angular-m2mpuv – JB Nizet Jan 08 '18 at 21:14
  • `the stream emits but never executes the success callback/function`. What does this mean? How do you test the stream emitting other than the subscription executing? – Pace Jan 08 '18 at 23:20
  • @Pace the observable has more than one subscription, other subscriptions don't have maps and filters and fire – Nikola Jankovic Jan 08 '18 at 23:41
  • @JBNizet, I have it working in another environment as well, but in production for whatever reason it's still glitchy when interacting with my other code. For now I've resolved to just having multiple subscriptions. I mostly wanted to see if there was an operator or known method to accomplish this - thanks for the help. – Nikola Jankovic Jan 08 '18 at 23:41

1 Answers1

5

It seems that the reason your code is not working is that objects that have the same properties and values are not considered equal in JavaScript, so distinctUntilChanged is not working because
{prop: "value"} === {prop: "value"} is false.

There's a simple solution. It's not recommended for long lists etc because it doesn't perform well under load, but if it's just route changes, that's not something that's going to change many times very quickly.

this.activatedRoute.queryParams
  .map((value) => {
    // Your existing code or
    const {page, ...otherProps} = value;
    return otherProps;
  })
  // You tell it what to use as comparison
  .distinctUntilChanged((oldProps, newProps) => 
    JSON.stringify(oldProps) === JSON.stringify(newProps)
  )
  // Not sure you still need this
  .skip(1)
  .subscribe(
    (value) => console.log(value)
  );

You can read for more ways to compare objects in Object comparison in JavaScript.

Update:

This answer got downvoted because JSON.stringify() is said to provide properties in different order sometimes.

The specifics of JSON.stringify() are not the core of the answer really, which is why I linked to another SO answer that focuses on the object comparison, however, simple googling showed yet another answer you can use: sort object properties and JSON.stringify

To follow the answer, you change this bit:

  .distinctUntilChanged((oldProps, newProps) => 
    JSON.stringify(oldProps) === JSON.stringify(newProps)
  )

into something like:

  .distinctUntilChanged((oldProps, newProps) => {
    // You can put this at the beginning of the file
    // It doesn't need to live inside the RxJS operator
    const createComparisonString = 
        (obj) => JSON.stringify(obj, Object.keys(obj).sort());

    return createComparisonString(oldProps) === createComparisonString(newProps);
  })

But again, JSON.stringify isn't the only way to compare objects. See the answer referenced above for examples of other options if you like. Again Object comparison in JavaScript.

Meligy
  • 35,654
  • 11
  • 85
  • 109
  • afaik, that won't actually work because oldProps and newProps are not guaranteed to be set in the same order. – Evan Carroll Jan 09 '18 at 06:04
  • It depends on the specific situation. It may well be usable if the objects are created from the same framework and the same way, but either way, I already linked to a better reference on SO about comparing objects. The question here is NOT how to compare objects but how to use comparison or whatever to make RxJS help with filtering. – Meligy Jan 09 '18 at 09:13
  • I still updated the answer with a fix for the sorting issue. Check it now. – Meligy Jan 09 '18 at 09:21
  • 2
    downvote was unjustified, but it is nice that you took the time to enrich the answer – user3743222 Jan 09 '18 at 15:54