Angular: finding out if Observable or BehaviorSubject has undergone any important modifications

I am facing an issue with my user object in a membership service.

I need to ensure that my services are updated only when there are relevant changes in the user object.

To determine if there are relevant changes in the user object, I compare it with the local user object and then update it accordingly.

However, this process is not working as expected.

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

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

      // It seems like this line always executes before the function call 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));
  }
}

I'm confused why the function hasUserRelevantChanges() keeps comparing the same new objects. The local this.user already contains the new values within this check, even though the assignment this.user = user comes afterwards?

How can I accurately determine if the new user object has any relevant value changes compared to the old/previous user object?

Answer №1

To achieve this, utilize RxJs operators. There is no need to store the user within your code; instead, you can use pipe along with pairwise and filter. The implementation would look something like this:

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

The code will be more organized if you transfer the logic to appropriate operators.

Note: Pairwise provides two values: previous and current, while filter selectively passes events based on a callback function returning true.

Additionally, ensure to implement userChanged or adjust hasUserRelevantChanges to accept 2 arguments.

Regarding why your current code may not work as expected, it's due to the behavior of Object.assign, which modifies the original object rather than creating a new reference each time.

Update: Consider using the spread operator:

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

}

Answer №2

"Why does my function hasUserRelevantChanges() always compare the same, new objects?"

The reason for this is that you constantly assign the new values to the this.user variable. To solve this issue, you should update it with the new user only if it actually differs from the previous one. Essentially, it should be placed 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 case, it would be more beneficial to utilize the RxJS pairwise + filter operators for checking 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(([user1, user2]) => {    // <-- Update 2: ignore `user1`
  this.updateMyServices(user2);
);

If you are indeed comparing all object properties with their previous values, you could replace the entire pairwise + filter setup with the distinctUntilChanged operator.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Separating the time and date into distinct variables offers flexibility in how they

Struggling with formatting time in a web component using TypeScript and React. The code below is working: new Date(myDate) .toLocaleTimeString( 'en-US', { weekday: 'short', year: 'numeric', month: 'short', ...

Is there a method to make changes to files on a deployed Angular application without the need to rebuild?

After deploying my Angular application on a production environment using the command npm run build --prod --base -href, I now need to make changes to some static HTML and TypeScript files. However, since the app is already bundled and deployed, I'm un ...

Exploring the world of ng2-translate for translating texts

For the translation of headings and texts in my Angular2 web application, I utilized ng2-translate. However, I am facing a dilemma when it comes to translating texts that are passed from a .ts file. For example, I can easily translate texts in an HTML fi ...

Rendering a React/Material UI class based on the state variable in a conditional manner

I am currently working on building a basic navbar setup using React and Material UI. I have encountered an issue where React-Router-Dom does not seem to be functioning within this particular project, and implementing it would be excessive for the simple ta ...

What is the best way to update an array in TypeScript when the elements are of different types and the secondary array has a different type as

const usersData = [ { "id": 0, "name": "ABC" }, { "id": 1, "name": "XYZ" } ]; let dataList = []; // How can I transfer the data from the user array to the dataList array? // If I use the map function, do I need to initialize empty values for oth ...

Simplify a function by lowering its cyclomatic complexity

This particular function is designed to determine whether a specific cell on a scrabble board qualifies as a double letter bonus spot. With a cyclomatic complexity of 23, it exceeds the recommended threshold of 20. Despite this, I am unsure of an alterna ...

Loading an Ionic module lazily within a children array is a smart way to

Within my Ionic application, I have structured a List Page Module and a Subdir Page Module nested under the main Page module. The folder structure looks like this ---> list/subdir. https://i.sstatic.net/XGrnU.png Dilemma: Whenever I navigate to localh ...

ngx-bootstrap: Typeahead, receiving an unexpected error with Observable

Encountering an error whenever more than 3 characters are typed into the input box. Error message: TypeError: You provided an invalid object where a stream was expected. Acceptable inputs include Observable, Promise, Array, or Iterable. .html file : < ...

The Uncaught SyntaxError issue arises when configuring webpack and Karma together

I am setting up webpack + karma + angular 2 and encountering a Uncaught SyntaxError : Unexpected token import. I am puzzled by the cause of this error. When I execute $karma start, it throws this error. Please assist me. Error START: webpack: bundle is ...

Problem with dynamic page routes in Next.js (and using TypeScript)

Hi everyone, I'm currently learning next.js and I'm facing an issue while trying to set up a route like **pages/perfil/[name]** The problem I'm encountering is that the data fetched from an API call for this page is based on an id, but I wa ...

Angular: it is impossible to access property 'x' as it is undefined

I am facing an issue where I keep getting the error message Cannot read property 'id' of undefined in my HTML component. <button (click)="getRecipeDetails()">Show</button> <div> <div [innerHTML]="recipeIn ...

How can we ensure that only one of two props is specified during compilation?

I've designed a customized Button component. interface Button { href?: string; action(): void; } I'm looking to ensure that when a consumer uses this Button, they can only pass either href or action as a prop, not both. I want TypeScri ...

Create HTML content from a file retrieved from the server

I have been working on a dynamic website project, diving into web development from scratch despite having coding experience in general. As I navigate Angular CLI and Bootstrap, I've come across a fundamental question: Do modern websites house all thei ...

A comprehensive guide on extracting data from the query string in Angular

There is a specific query string format that we need to handle. The input parameter of the method comes in the form of a string and it's not an instance of ActivatedRoute. http://localhost:4200/users?param1=en&param2=nk I've attempted to rea ...

Dynamically setting properties in a Vue component using Angular

After browsing through this interesting discussion, I decided to create a web component: <my-vue-web-comp [userId]="1"></my-vue-web-comp> Initially, everything was working smoothly in Angular when I assigned a static property. Howeve ...

Experimenting with a module reliant on two distinct services

I am facing an issue with a component that relies on a service to fetch data. The service also retrieves configurations from a static variable in the Configuration Service, but during Karma tests, the const variable is showing up as undefined. Although I ...

What is the best method for retrieving the complete path of a FormControl in Angular versions 4 and above

Is there a way to obtain the complete path of a FormControl in Angular 4+? Below is my reactive form structure: { name: '', address: { city: '', country: '' } } I urgently require the full path o ...

Prevent HTTP using AsyncValidator when the value is empty

I have developed a custom AsyncValidator to verify the uniqueness of a userName. Inspired by this tutorial, I have implemented a delay of 500ms. However, I am facing a challenge in preventing the HTTP service call if the input value does not meet a speci ...

Having trouble applying CSS while printing using the ngx-print library in Angular 14. Can anyone help me out with this issue

The table shown in the image is experiencing issues with applying CSS properties when printing. While the background graphics feature has been enabled, the preview section still does not reflect the CSS styling. What could be causing this discrepancy? Cli ...

MongoDB table collections (table names in other databases)

After setting up my express server to connect to mongodb, I encountered an issue despite everything working fine initially. I created a collection in my mongodb called projects (plural form). In my project.model.js file, I defined the model as follows: c ...