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

The useEffect hook is triggering multiple unnecessary calls

Imagine a tree-like structure that needs to be expanded to display all checked children. Check out this piece of code below: const { data } = useGetData(); // a custom react-query hook fetching data from an endpoint Now, there's a function that fin ...

Sort your list efficiently with a custom hook in React using Typescript

I've been working on developing a custom hook in React that sorts an array based on two arguments: the list itself and a string representing the key to sort by. Despite trying various approaches, I haven't been able to find a solution yet. I&apos ...

The React Hook Form's useFieldArray feature is causing conflicts with my custom id assignments

My schema includes an id property, but when I implement useFieldArray, it automatically overrides that id. I'm utilizing shadcn-ui Version of react-hook-form: 7.45.0 const { fields, append, remove, update } = useFieldArray<{ id?: string, test?: n ...

Translating Python's slicing assignment syntax to JavaScript/TypeScript: A guide

Currently, I am in the process of converting a Python library into TypeScript. One specific challenge I am facing is translating this line of code from this particular repository: is_prime[start - segment_min::pk] = repeat(False, len(range(start - segment ...

Angular: Observing changes in the store and sending a message from a Service component to another component once the Service has finished specific tasks

Within our codebase, we introduce two classes known as GetDataAsyncService. This service is designed to wait for a change in the store before executing the block of code contained within it. By utilizing observables and subscribing to data changes with t ...

I am having trouble getting the guide for setting up a NextJS app with Typescript to function properly

For some time now, I have been experimenting with integrating Typescript into my NextJS projects. Initially, I believed that getting started with Typescript would be the most challenging part, but it turns out that installing it is proving to be even more ...

Discovering the tab index of a tab header in Angular 4 Material

In my Angular application, I am using a mat-tab component to display tabs dynamically generated from an array. The template looks something like this: <mat-tab-group> <mat-tab *ngFor="let tb of dynTabs"> ...

Exploring Angular and Typescript - attempting to adjust cursor position for multiple child elements within a contenteditable div despite researching numerous articles on the topic

I could use some assistance in resolving this issue. Here is the stackblitz code I am currently working with If you have any workarounds, please share them with me. The caret/cursor keeps moving to the starting position and types backward. Can anyone hel ...

Guide on accessing the afterClosed() method / observable in Angular from a Modal Wrapper Service

Currently, I am in the process of teaching myself coding and Angular by developing a personal app. Within my app, I have created a wrapper service for the Angular Material ModalDialog. It's a mix of Angular and AngularJS that I've been working on ...

Encountering a "Error: Uncaught (in promise): EmptyError: no elements in sequence" due to the presence of multiple Angular 9 Route Resolvers

Why do I encounter an error when attempting to use multiple resolvers in Angular routing? If I remove one of the route resolves, everything works fine. But as soon as I include both, the error occurs. https://i.stack.imgur.com/WFI5C.png https://i.stack.im ...

Discovering all invalid elements in an Angular 8 Form using Typescript by revealing required fields post button click

Once the button is clicked, I want to retrieve all invalid elements in the Form and showcase those fields that are either incomplete or required. ...

Is it possible to use jQuery to set a value for a form control within an Angular component?

I'm currently working on an Angular 5 UI project. In one of my component templates, I have a text area where I'm attempting to set a value from the component.ts file using jQuery. However, for some reason, it's not working. Any suggestions o ...

Identify any missing periods and combine the years into a single range

I am working on restructuring year ranges with gaps and consolidating them. For example, converting [{start: 2002, end: 2020}, {start: 2020, end: null}] to {start: 2002, end: null} or [{2002, 2004},{2006, 2008}, {2008, null}] to [{2002-2004}, {2006-null}]. ...

The element of type 'OverridableComponent<LinkTypeMap<{}, "a">>' cannot be assigned to a 'ReactNode'

I'm currently working on a project where there's a component named ListItemTextStyle. Within that component, the prop type is defined as follows: import { LinkProps, ListItemButtonProps, } from '@mui/material'; type IProps = LinkP ...

Explanation of Default Export in TypeScript

I recently started learning about JS, TS, and node.js. While exploring https://github.com/santiq/bulletproof-nodejs, I came across a section of code that is a bit confusing to me. I'm hoping someone can help explain a part of the code. In this project ...

The feature 'forEach' is not available for the 'void' type

The following code is performing the following tasks: 1. Reading a folder, 2. Merging and auto-cropping images, and 3. Saving the final images into PNG files. const filenames = fs.readdirSync('./in').map(filename => { return path.parse(filen ...

"Once the queryParams have been updated, the ActivatedRoute.queryParams event is triggered once

Within my Angular component, I am making an API call by passing a hash string extracted from the current query parameters. Upon receiving the API result, a new hash is also obtained and set as the new hash query parameter. Subsequently, the next API call w ...

Converting JSON Arrays into Typescript Arrays

I am working with a JSON file that contains an array object like this: [ { "VergiNo": "XXXXXXX" }, { "VergiNo": "YYYYYY" }, { "VergiNo": "ZZZZZZ" } ] After importing this JSON file into my Typescript file, import * as companies f ...

Custom type declaration file in Typescript fails to function properly

I have searched through countless solutions to a similar issue, but none seem to work for me. I am attempting to utilize an npm package that lacks TypeScript type definitions, so I decided to create my own .d.ts file. However, every time I try, I encounter ...

Activate a function with one event that is triggered by another event in Angular 5 and Material Design 2

I am facing an issue where I need to change the value of a radio button based on another radio button selection in Angular 5 with Material Design 2. However, the event is not triggering and there are no console errors being displayed. For example, if I cl ...