2

I have the need to update properties of an observable object. I managed to make it work, but I don't really like how I did it.

next(filter$: BehaviorSubject<GridCollectionFilter>, collection: GridCollectionModel<any>) {
  let filter = filter$.value;

  if (!filter.page) {
    filter.page = 1;
  }

  filter.page++;

  if (filter.page > collection.pageCount) {
    filter.page = collection.pageCount;
  }

  this.updateFilter(filter$);
}

first(filter$: BehaviorSubject<GridCollectionFilter>) {
  filter$.value.page = 1;
  this.updateFilter(filter$);
}

last(filter$: BehaviorSubject<GridCollectionFilter>, collection: GridCollectionModel<any>) {
  filter$.value.page = collection.pageCount;
  this.updateFilter(filter$);
}

updateFilter(filter$: BehaviorSubject<GridCollectionFilter>, filter?: GridCollectionFilter) {
  filter$.next(_.clone(filter || filter$.value));
}

Is there a more elegant way to update the filter$ value object in this case?

I really dislike the idea of dog-feeding the clone of the observable to itself by doing filter$.next(_.clone(filter$.value)) every time I simply want to update a few properties of the observed object through the code. I'm using _.clone() to break the reference (otherwise rxjs distinctUntilChanged() doesn't trigger for obvious reasons).

Is there some kind of rxjs extension which could shorten what I'm doing? I'm thinking of something along the lines of filter$.commitValueChanges() which would internally do filter$.next(_.clone(filter$.value)).

I'm pretty new to observables so I'm not sure if I'm thinking about this in a completely wrong way in the first place. I understand I'm supposed to commit the new state when I want to trigger the change listeners. What I'm not sure is whether you're ever supposed to modify the filter$.value directly? Or are you typically supposed to have two objects - filter (the filter) and filter$ (observable of filter) and then absolutely never mutate the filter$ directly but simply submit filter to .next() every time a change on filter occurs? But that would then seem redundant because we now have two objects, one which we're changing through the app and have to handle manually by attaching various event handlers and all that just to be able to update the other one - filter$. That seems excessive and hacky to me.

Thnx.

3 Answers 3

1

To remove _clone() from your code you should use distinctUntilKeyChanged(). It will check if any key in object were change.

DistinctUntilKeyChanged API

But the better aproach is:

filter$: BehaviorSubject<GridCollectionFilter>;
collection: GridCollectionModel<any>;
currentFilter: GridCollectionFilter; 

ngOnInit() {
  this.filter$.subscribe(filter => {
    this.currentFilter = filter;
    // Here for example invoke rest api with filter to change collection
  })
}

next() {
  if (!this.currentFilter.page) {
    this.currentFilter.page;
  }

  this.currentFilter.page++;

  if (this.currentFilter.page > this.collection.pageCount) {
    this.currentFilter.page = collection.pageCount;
  }

  this.filter$.next(this.currentFilter);
}

first() {
  this.currentFilter.page = 1;
  this.filter$.next(this.currentFilter);
}

last(filter$: BehaviorSubject<GridCollectionFilter>, collection: GridCollectionModel<any>) {
  this.currentFilter.page = collection.pageCount;
  this.filter$.next(this.currentFilter);
}
Sign up to request clarification or add additional context in comments.

5 Comments

I like the distinctUntilKeyChanged() bit, that's very helpful, I wasn't aware of that. But I wouldn't be a big fan of adding the additional currentFilter field as it adds additional complexity and duplicates the code in a way. I'm trying to avoid having two filter objects and am of the opinion a single one should be able to do the trick. Having two of them feels hacky and messy.
The methods I pasted are actually in a service class, not in a component. The filter$ observable is something which is created by a custom BaseGridComponent and then the event handler methods (next, first last and a few other ones I didn't paste) are supposed to update the filter$ if that makes sense. The filter$.value is what gets observed and when it changes, the filter object gets sent to the backend to fetch the data.
So move it to the component BaseGridComponent . In grid which you extend by BaseGridComponent you start listen for filter change and then invoke rest api to reload collection. What you think ?
I thought a bit more about this, I'll test a few ideas a bit later and get back then. In the meantime perhaps I get a few more answers/ideas here.
Ok, I watched a full course about RxJs in Angular and now I have a better understanding of the whole thing and what you suggested (having a separate filter which is used to update filter$) seems like the default way to go. Thnx.
1

For what it's worth, I understand your problem completely - as I'm struggling with the exact same issue. I've bumped into numerous Angular change detection issues, because I was modifying the value of subject directly. Now I'm just _.cloneDeep the value, modify it and then feed it back to the subject with .next(). I, too, would appreciate some sort of shortcut for the subject to detect changes and publish new object. For the time being, I haven't found better solution.

Comments

0

If your only problem is that you need to clone the objects maybe one of these tips will help you:

  • The distinctUntilChanged operator compares object identity by default. However it takes an optional compare function that you can use to implement any comparison login you want.

    .distinctUntilChanged((a, b) => a.page === b.page)
    

    You can also use it to compare two different instances with identical properties

    .distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
    
  • If you're ok with creating just shallow copies you can use one of these:

    Object.assign({}, filter)
    

    or this:

    { ...filter }
    

    Both of these make a shallow copy.

  • If you need a deep copy and you're not worried about performance (seems like you have very small objects) you can use JSON:

    JSON.parse(JSON.stringify(filter))
    

    This way you don't need any 3rd party library.

3 Comments

It's actually not only the clone. I don't mind having _.clone() (and in general lodash) since I have it by default in all my apps because it's a very useful lib. It's just feels that feeding .next() with it's own cloned observable value is a bit of an overhead for something as simple as updating a property or two. I feel that having a simple method called .nextSelf() would reduce the amount of code. But I need to research observables a bit more before I make that judgement. Right now I'm just looking for ways to simplify a bit.
I think I don't understand what your code should be doing.
I didn't want to bore everyone with the whole thing because I thought it would be too much noise around what my question is really about. I am simply wondering if there is some sort of filter$.commitValueChanges() which would replace the need for filter$.next(_.clone(filter$.values)) altogether. I'll update my question a bit. In any case, thanks for writing up an answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.