Issue with Angular project: View not being updated when using behaviorSubjects

Within my Angular project, I am retrieving data from an API using a service and storing the data within a BehaviorSubject as shown below

  private categorySubject = new BehaviorSubject<number | null>(null);
  apiBehavior = new ReplaySubject<ApiResponseInterface>();

  constructor(private http: HttpClient, private authService: AuthService) {}
  getQuestions(page: string = '', category = this.categorySubject.getValue()): Observable<ApiResponseInterface> {
    let url = this.baseUrl;
    if (category) {
      console.log(category);
      url = this.baseUrl + '/category/' + category;
    }
    const params = new HttpParams().set('cursor', page);
    return this.http.get<ApiResponseInterface>(url, { params })
      .pipe(tap((res) => this.apiBehavior.next(res)));
  }

In my component, I am subscribing to the behavior subject like this:

ngOnInit() {
    this.questionsService.getQuestions()
      .pipe(takeUntil(this.destroyed))
      .subscribe((res) => {
        this.apiResponse = res;
        this.questions = res.data;
      });
  }

Everything is working fine up to this point. However, I have another component that sends a new value to the categoryBehaviorSubject within the service using this function:

setCategorySubject(categoryId: number | null) {
    this.questionsService.setCategory(categoryId);
  }

The setCategory function in my service updates the categoryBehaviorSubject with the new value and also updates the apiBehavior like this:

setCategory(categoryId: number | null) {
    this.categorySubject.next(categoryId);
    this.getQuestions();
  }

This is the template of my component which should display questions:

<main
  *ngIf="questions"
  class="p-6 lg:p-20 flex max-md:flex-col gap-6 align-items-center h-full md:justify-between w-full"
>

  <section class="flex flex-col gap-6 align-items-center md:w-4/5 lg:w-2/5">
    <app-category-selector
      class="flex justify-center align-items-center mt-6 md:hidden"
    >
    </app-category-selector>
    <header class="font-primary text-bold text-lg text-center"><h1>{{questions.length}} Posts</h1></header>

    <app-question
      *ngFor="let question of questions"
      [question]="question"
      class="bg-secondary flex p-4 rounded h-1/4"
    ></app-question>
</main>

The issue here is: even though the data is successfully fetched from the API and passed to the apiBehavior Subject, the view is not updated, and new questions are never displayed when a category has been set. What could be causing this discrepancy?

Answer №1

Within my service, the setCategory function sends the new value to the categoryBehaviorSubject and updates the apiBehavior as follows:

setCategory(categoryId: number | null) {
this.categorySubject.next(categoryId);
this.getQuestions(); // does NOT trigger the request
}

You mentioned that it "updates the apiBehavior", however, this is inaccurate. Invoking the getQuestions() function does not prompt a new request; you can monitor your Network tab in the developer tools.

Because another request is not initiated, the code within this block will not be executed either:

return this.http.get<ApiResponseInterface>(url, { params })
  .pipe(tap((res) => this.apiBehavior.next(res)));

Why isn't another request triggered when getQuestions() is called from setCategory?

This is because getQuestions() returns an Observable, and nothing happens until it is subscribed to.

You might argue

But I already subscribed to it in ngOnInit

No, you subscribed to a different Observable there. Once the initial request is completed, the Observable created by the http client is terminated. Subsequently calling getQuestions() generates a new Observable which also requires subscription.

The key point to note is: invoking this.http.get() without subscribing means NO request is made. And no request implies that the pipe operation will not be executed.

If the explanation provided thus far is unclear, I recommend reading the following article for further clarification:

Now, how can you enhance your design?

While I cannot guarantee this is the optimal solution, here is a suggestion:

Create an additional EventEmitter, perhaps named QuestionsObservable (or maybe a BehaviorSubject, depending on your requirements), and push the retrieved data into it every time you fetch the questions.

In the component responsible for displaying the questions, subscribe to QuestionsObservable.

Give it a try, and feel free to reach out if you have any more queries.

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

Looking for guidance on converting JS code to TypeScript? Let's tackle this TS test together!

I am facing the challenge of encapsulating a very complex SDK into a layer of code. I have attempted to utilize union and index types for this task, and below is a demo that I have created. How can I implement the bar method in TypeScript to pass the conso ...

Pause and be patient while in the function that delivers an observable

I have a function that loads user details and returns an observable. This function is called from multiple places, but I want to prevent redundant calls by waiting until the result is loaded after the first call. Can anyone suggest how this can be accompli ...

When making Angular GET requests, the response may return a single Object instead of an Array

When making a GET request to my API, the data is returned as Objects inside an Object instead of an Array of Objects. This makes it difficult for me to iterate through and print the data. I am puzzled by why this specific request is behaving differently c ...

What is the best way to retrieve a property with a period in the method name in JavaScript?

One dilemma I'm facing is trying to access the tree.removenode method through chartContext in Angular. It's been a challenge for me to understand how to achieve this. https://i.stack.imgur.com/yG7uB.png ...

Typescript excels at gracefully handling cases where an element is not found

While working with Typescript-Protractor Jasmine, I encountered an issue where the test case (the 'it' block) is not failing when an element is not found. Instead, it shows an UnhandledPromiseRejectionWarning but still marks the script as passed. ...

What is the best way to determine Prisma types across various projects?

My current project has the following structure: dashboard -- prisma-project-1 -- prisma-project-2 -- client-of-prisma-project-1-and-prisma-project-2 This dashboard is designed to merge data from two separate databases and display them in a meaningful w ...

Embedding a module within a fluid element

Creating a dynamic component and projecting template inside it can be done easily with the following code snippet - @Component({ selector: 'dynamic', template: ` <p>Dynamic Component</p> <ng-content></ ...

Trouble encountered with uploading files using Multer

I am facing an issue with uploading images on a website that is built using React. The problem seems to be related to the backend Node.js code. Code: const multer = require("multer"); // Check if the directory exists, if not, create it const di ...

Incorporate matter-js into your TypeScript project

Upon discovering this file: https://www.npmjs.com/package/@types/matter-js I ran the following line of code: npm install --save @types/matter-js When I tried to use it in the main ts file, an error message appeared: 'Matter' refers to a U ...

Ways to access the value of the parent observable?

I've been exploring the concept of nesting HTTP requests using mergeMap. The API I'm working with sends data in parts, or pages, which means I have to make more requests based on the total number of pages. To determine the number of pages, I alw ...

What is the reason behind the term "interpolation" for the double curly braces in Angular/

Even after over a year of experience with Angular/JS, I find myself struggling to truly grasp the concept of interpolation (for example, {{1+4}}). Can you explain the origin of this term in the context of Angular/JS and if it shares any similarities with ...

Error in Typescript: Function not being triggered on button click

As someone who is just starting out with typescript, I've been tackling a simple code that should display an alert message in the browser when a button is clicked. I've experimented with the button and input tags, as well as using both onclick ev ...

Angular2 - trigger an HTTP GET request from a different component

At my workplace, I mainly use AngularJS (1.5) but recently ventured into creating my first Angular2 application. However, I've encountered a slight issue with observables. The service I'm working with is the TicketService: import { Injectable, ...

Creating a function in TypeScript that returns a string containing a newline character

My goal is to create a function that outputs the text "Hello" followed by "World". However, my current implementation does not seem to be working as expected. export function HelloWorld():string{ return "Hello"+ "\n"+ "W ...

There was an error in parsing the module: an unexpected token was encountered during the rendering

Recently, I've been working on configuring React with Typescript (for type checking), Babel for code transpilation, Jest for testing, ESLint for code checking, and a few other tools. You can find all the necessary files in the repository linked below. ...

Velocity: The initial parameter was not recognized as a property mapping

I've been experimenting with Velocity for animations (without jQuery), but I'm running into an issue where I keep getting this error message: Velocity: First argument ([object HTMLDivElement]) was not a property map, a known action, or a regis ...

Leverage both props and destructuring in your Typescript + React projects for maximum efficiency!

Is it possible to use both destructuring and props in React? For instance, can I have specific inputs like name and age that are directly accessed through destructuring, while also accessing additional inputs via props? Example The desired outcome would ...

Sharing a variable between an Angular component and a service

I am attempting to pass a variable from a method to a service. from calibration-detail.component.ts private heroID: number; getTheHeroID() { this.heroService.getHero(this.hero.id).subscribe(data =>(this.heroID = data.id)); } to step.service.ts I ...

You cannot assign multiple properties with the same name to an object literal

I am facing an issue with two validator functions in my TypeScript file. The first validator checks if a user enters a new password same as the old one, displaying an error if so. The second validator ensures that "new password" and "confirm password" must ...

Adding Components Dynamically to Angular Parent Dashboard: A Step-by-Step Guide

I have a dynamic dashboard of cards that I created using the ng generate @angular/material:material-dashboard command. The cards in the dashboard are structured like this: <div class="grid-container"> <h1 class="mat-h1">Dashboard</h1> ...