Using `publishReplay()` and `refCount()` in Angular does not behave as anticipated when dealing with subscriptions across multiple components

I am currently investigating the functionality of publishReplay in rxjs. I have encountered an example where it behaves as expected:

const source = new Subject()
const sourceWrapper = source.pipe(
  publishReplay(1),
  refCount()
)

const subscribeTest1 = sourceWrapper
.subscribe(
  (data: any) => {
    console.log('subscriber1 received a value:', data)
  }
)

source.next(1)
source.next(2)

setTimeout(() => {
  const subscribeTest2 = sourceWrapper
  .subscribe(
    (data: any) => {
      console.log('subscriber2 received a value:', data)
    }
  )
}, 5000)

We have a subject and a wrapper on it with publushReplay(1), refCount() included. We establish the initial subscription and then emit 2 values. As a consequence, the following appears in the console:

subscriber1 received a value: 1
subscriber1 received a value: 2

After 5 seconds, we initiate another subscription, receiving the most recent buffered value from the ReplaySubject created by publishReplay(1). Consequently, after 5 seconds, an additional message will be shown in the console:

subscriber2 received a value: 2

Link to stackblitz

Everything seems to be functioning correctly so far.

However, I have come across another scenario where I attempt to implement this concept in an Angular application. Here is the link.

This setup consists of a single module containing two components: app.component.ts, hello.component.ts, and one service: test.service.ts

test.service.ts

@Injectable({
  providedIn: 'root'
})

export class TestService {
  private _test$: Subject<string> = new Subject()

  public get test$(): Observable<string> {
    return this._test$.pipe(
      publishReplay(1),
      refCount()
    )
  }

  public pushToStream(val: string): void {
    this._test$.next(val)
  }
}

app.component.ts

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  public name = 'Angular';

  constructor(private testService: TestService) {}

  public ngOnInit(): void {
    this.testService.test$.subscribe((data: string) => {
      console.log('Subscriber in app.component received a value: ', data)
    })
    this.testService.pushToStream('new value')
    this.testService.pushToStream('one more value')
  }
}

app.component.html

<hello name="{{ name }}"></hello>
<p>
  Start editing to see some magic happen :)
</p>

hello.component.ts

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit  {
  @Input() name: string;

  constructor(private testService: TestService) { }

  public ngOnInit(): void {
    setTimeout(() => {
      this.testService.test$.subscribe((data: string) => {
        console.log('Subscriber in hello component received a value:', data)
      })
    }, 4000)
  }

}

In this case, we follow the same principle: maintaining the source in a service, which acts as a singleton, creating 1 subscription in app.component.ts, emitting 2 values, and setting up another subscription with a delay in hello.component.ts. As seen in the previous example, the second subscription should retrieve the latest buffered value, but it does not. Only the following messages are displayed in the console:

Subscriber in app.component received a value: new value
Subscriber in app.component received a value: one more value

What could be the missing piece causing it to fail?

Answer №1

When using an accessor (getter) or a member function to return the value, it seems that the reference to the subject wrapper is lost. However, if you declare the value test$ as public and access it directly, the reference to the source observable remains intact across different components. You can try the following:

Service

export class TestService {
  private _test$: Subject<string> = new Subject()

  public test$ = this._test$.pipe(
    publishReplay(1),
    refCount()
  );

  public pushToStream(val: string): void {
    this._test$.next(val)
  }
}

I've made some modifications to your Stackblitz

It's possible that the functions are returning separate copies of variables that are connected to the same subject observable. Although they are not the same instance, each test$ in the components may be its own individual instance.

Perhaps someone else can provide a more definitive answer.

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

Erase Typescript Service

To remove a PostOffice from the array based on its ID, you can use a checkbox to select the desired element and then utilize its ID for the delete function. Here is an example: let postOffices = [ {postOfficeID: 15, postCode: '3006&ap ...

Unveiling the Power of Angular 4's HttpClient for Eff

I have encountered an issue with my API, where it returns ISO date that I need to convert into a JavaScript date. Despite using the HTTPClient module for automatic mapping, the data received is not being transformed as expected. While I am aware that it c ...

Troubleshooting: After Updating to Angular 8.3.5, App Module and Components Fail to Load in Angular Application

Recently, I attempted to upgrade our Angular 4 project to Angular 8. Following the migration guide provided by Angular, I made syntax changes. However, I encountered issues with styles not loading properly both locally and on Azure. To resolve this, I deci ...

Altering a public variable of a component from a sibling component

Within my application, I have two sibling components that are being set from the app.component: <my-a></my-a> <my-b></my-b> The visibility of <my-a> is determined by a public variable in its component: @Component({ module ...

Tips for transferring ID from one Angular page to another page?

I'm wondering if there is a way to pass the id from one page to another without relying on Routing or local storage. Storing it in the service file causes it to collapse upon page refresh, so I am looking for alternative solutions. Any suggestions wou ...

Getting started with Angular2 ngrx storemodule reducer - Understanding the process of injecting services into the initialState

const startingState = { // service.fetch(); } How can we inject a service into a reducer? Is it possible to utilize a service within a reducer without a constructor or class? export function personnelHandler(state = startingState, action: PersonnelAction ...

Using Typescript with Gulp 4 and browser-sync is a powerful combination for front-end development

Could use some assistance with setting up my ts-project. Appreciate any help in advance. Have looked around for a solution in the gulpfile.ts but haven't found one yet. //- package.json { "name": "cdd", "version": "1.0.0", "description": "" ...

Angular is encountering a CORS issue with the "No Access-Control-Allow-Origin" error when trying to access a CSS file

Currently, in my Angular 5 application with a Web API back-end, my main goal is to reference a CSS file on a production server. I'm facing a cross-origin issue when trying to access the CSS file hosted on the IIS server website. Even though CORS is e ...

Error: npm encountered a loop error while attempting to download

Looking to implement Google login, I attempted the following command: npm install --save angularx-social-login. Unfortunately, it returned an error: D:\proj>npm install --save angularx-social-login npm ERR! code ELOOP npm ERR! syscall open npm ERR ...

Guide on sending information from a parent component to a child component in Angular using an event system

Is it possible to pass data from a parent component to a child component in Angular using a tab group? <mat-tab-group> <mat-tab label="Some text0"> <app-comp></app-comp1> </mat-tab> <mat-tab label="Some ...

Error: The StsConfigLoader provider is not found! MSAL angular

I am currently using Auth0 to manage users in my Angular application, but I want to switch to Azure Identity by utilizing @azure/msal-angular. To make this change, I removed the AuthModule from my app.module and replaced it with MsalModule. However, I enco ...

Provide information to spyOn and return a specific value

I am attempting to mimic a call to a service that involves an HTTP call. My goal is to provide fabricated data in the mock and verify it during my test. This is how I have set up the scenario: beforeEach(() => { fixture = TestBed.createComponent(MhS ...

Learn how to store the outcomes of an HTTP operation within array.map() in JavaScript

Having read numerous articles, I am a complete beginner when it comes to async programming and struggling to grasp its concepts. My goal is to map a filtered array of objects and return the result of a function (an amount) to set as the value of pmtdue. De ...

How can I implement a dynamic form to display only when there are values available for the specified ID?

https://i.stack.imgur.com/imqYb.pngI am dealing with an object that is coming from the backend, containing template parameters such as "namespace,resources". In some cases, the template parameter value is "null". My goal is to display a form only when ther ...

Refill ag-grid with fresh data

Setting up the ag-grid initialization directly from the HTML using an onGridReady method in the component file. <div style="flex-grow:1;"> <ag-grid-angular style="float:left;width: 100%; height: 201px;margin-top:10px;" class="ag- ...

What could be the reason for the crash caused by ngModel?

The usage of [(ngModel)] within a *ngFor-Loop is causing an endless loop and crashing the browser. This is how my HTML looks: <div class="container"> <div class="row" *ngFor="let item of controlSystemTargetViewModel.values; let index = i ...

Cancel the previous Angular/RxJS request to unsubscribe

I'm on the quest for a more streamlined approach using RxJS to tackle this task. The existing code gets the job done, but it seems like there should be a more efficient solution. ngOnInit() { this.activatedRoute.params.subscribe(({ bookId }) => ...

The term 'required' is not recognized as an identifier. There is no member by the name of '__type' in the definition

When working on my HTML template in the visual code editor, I encountered the need to declare a variable with type any? https://i.stack.imgur.com/Jq5az.png product-form.component.html <div class="row"> <div class="col-md-6"> <for ...

A guide on creating a function that can detect if an object is not iterable and then throw an error

Exploration Uncomfortable type definition at the library: declare type Extension = { extension: Extension; } | readonly Extension[]; Type-validation function export function isIterable(x: any): x is Iterable<unknown> { return Symbol.iterator ...

Creating TypeScript models from a JSON response in React components

My Angular 2 application retrieves a JSON string, and I am looking to map its values to a model. According to my understanding, a TypeScript model file is used to assist in mapping an HTTP Get response to an object - in this case, a class named 'Custo ...