Home > Back-end >  Angular: check if Observable / BehaviorSubject has relevant changes
Angular: check if Observable / BehaviorSubject has relevant changes

Time:11-21

I have a user object which I observe in a membership service.

I want to update my services only if the user object has relevant changes.

To understand if my user object has relevant changes, I compare my local user object with the observed one. And always assign the new object afterwards.

This does not work, however.

export class MemberService {
  private subscription?: Subscription;
  user?: User;

  constructor(public auth: AuthService) {
    this.subscription = this.auth.user$.subscribe((user) => {
      const updateServices = this.hasUserRelevantChanges(user)

      // apparently this line always fires before the functioncall above?!
      this.user = user;

      if (updateServices) {
         this.updateMyServices();
      }
    });
  }

  ngOnDestroy() {
    this.subscription?.unsubscribe();
  }

  hasUserRelevantChanges(user: User | undefined): boolean {
      return user?.subscription !== this.user?.subscription ||
          user?.username !== this.user?.username ||
          user?.isBanned !== this.user?.isBanned;
  }

  updateMyServices(): void {
    // Updating my services!!!
  }
}

export class AuthService {
  public readonly user$: Observable<User| undefined> = this.user.asObservable();
  user: BehaviorSubject<User| undefined> = new BehaviorSubject<User| undefined>(undefined);

    constructor(private httpHandler: HttpHandlerService) { ... }

  handleUser(): void {
    this.httpHandler.login().subscribe(
        (user: User) => this.user.next(user));
  }

  updateUserData(newUser: User): void {
    this.user.next(Object.assign(this.user.value, newUser));
  }
}

How come that my function hasUserRelevantChanges() always compares the same, new objects? The local this.user always holds the new values already within this check, eventhough the assignment this.user = user comes afterwards?

So how can I understand if my new user object has the relevant values changed in comparison to the old/previous user-object?

Thanks in advance!

CodePudding user response:

Use RxJs operators to do so. You don't need to store user somewhere in your code but you can pipe it with pairwise and filter. So it would be something like this:

this.auth.user$.pipe(
           pairwise(), 
           filter(users => userChanged(users[0], users[1]))
           ... // other operators to handle change - tap, map etc
           )

It will be much cleaner if you move logic to proper operators.

Edit: Pairwise returns two values: previous and current, filter - as the name says filter events and emits next only if passed callback returns true

Edit2: You should implement userChanged or adjust your hasUserRelevantChanges which now you would need to pass 2 arguments.

Edit3: Why your code doesn't work? Because Object.assign doesn't create new object reference but change original object - so in fact, you are checking everytime the same object.

CodePudding user response:

"How come that my function hasUserRelevantChanges() always compares the same, new objects?"

That's because you're always assigning the new values to the this.user variable. Ideally you'd need to update it with the new user only if it's different. In other words, it must be moved inside the if block.

this.subscription = this.auth.user$.subscribe((user) => {
  const updateServices = this.hasUserRelevantChanges(user)

  if (updateServices) {
    this.user = user;
    this.updateMyServices();
  }
});

Update: pairwise filter

In your scenario, you would do better to use the RxJS pairwise filter operators to check the condition.

this.subscription = this.auth.user$.pipe(
  pairwise(),
  filter(([user1, user2]) => (
    user1?.subscription !== user2?.subscription ||
    user1?.username !== user2?.username ||
    user1?.isBanned !== user2?.isBanned;
  ))
).subscribe((user) => 
  this.updateMyServices();
);

If you're actually comparing all the properties of the object with it's previous values, you could replace the entire pairwise filter with the distinctUntilChanged operator.

  • Related