Utilizing a foundational element to automatically unsubscribe from multiple observable subscriptions

Within our Angular application, we have implemented a unique concept using a Base Component to manage observable subscriptions throughout the entire app. When a component subscribes to an observable, it must extend the Base Component. This approach ensures that all subscriptions remain active until the entire application is finally destroyed, rather than being destroyed with each individual component:

base.component.ts:

import { Subject } from 'rxjs';
import { OnDestroy, Component } from '@angular/core';

export abstract class BaseComponent implements OnDestroy {
  protected unsubscribe$ = new Subject<void>();

  ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}

the-rest-of-our-components.ts:

import { Component, OnInit } from '@angular/core';
import { MyService } from 'src/app/services/my.service';
import { BaseComponent } from '../base/component/base-component';

export class myComponent extends BaseComponent implements OnInit {
  myProperty: string;

  constructor(
    private myService: MyService,
  ) {
    super();
  }
  ngOnInit(): void {
    this.myService.doStuff$
      .pipe(takeUntil(this.unsubscribe$)) // take until baseComponent's unsubscribe$
      .subscribe((data) => {
        this.myProperty = data;
      });
  }


If multiple components extend BaseComponent and make use of its unsubscribe$ Subject, do all the subscriptions only get unsubscribed when the entire application is closed (when Base Component is destroyed), as opposed to when individual components are destroyed?

Is this an effective strategy you've encountered before? Is it recommended? If my assumptions hold true, it means that all subscriptions in our application will persist until the entire app is terminated. Depending on our requirements, this could be beneficial or detrimental.

Bonus question: Does Base Component act like a singleton? In other words, if multiple components simultaneously extend BaseComponent, do they share the same instance of unsubscribe$, or does each component have its own separate instance?

Answer №1

I decided to put my assumption to the test rather than relying on it, as we all know where assumptions can lead us: https://stackblitz.com/edit/angular-ivy-ueshwz?file=src/app/extended/extended.component.ts

Through experimentation, I found that subscriptions are indeed destroyed when individual components are destroyed.


To demonstrate this, I created a service with a subject for subscription and a value that can be modified through the subscription:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/internal/Subject';

@Injectable({ providedIn: 'root' })
export class UpdateService {
  subject = new Subject<void>();
  value = 0;
}

In the main component, I continuously trigger the subject every second and have a toggleable subcomponent:

export class AppComponent implements OnInit {
  extCompOpen = true;

  constructor(public update: UpdateService) {}

  ngOnInit() {
    interval(1000).subscribe(() => this.update.subject.next());
  }
}
<app-extended *ngIf="extCompOpen"></app-extended>
<button (click)="extCompOpen = !extCompOpen">Toggle Component</button>

<p>The counter will increment as long as the subscription is active:</p>
<p>{{ update.value }}</p>

Furthermore, an extended subcomponent increments the value by 1 using the subscription:

export class ExtendedComponent extends BaseComponent implements OnInit {
  constructor(private update: UpdateService) {
    super();
  }

  ngOnInit() {
    this.update.subject.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
      this.update.value++;
    });
  }
}
<p>Extended component functionality confirmed!</p>

Interestingly, closing the subcomponent halts the incrementation process, indicating that the subscription has been successfully unsubscribed.


A bonus question arises regarding BaseComponent's behavior as a singleton - creating individual instances does not result in shared parent class instances. Extending a class simply adds properties and methods to the specific instance.


While this approach may work, caution is advised since overriding ngOnDestroy() requires calling super.ngOnDestroy(), which could easily be overlooked. With only four lines of code, manually including it in each component might be a safer practice. Manual subscriptions should ideally be infrequent, especially when utilizing the async pipe.

Answer №2

A solution I implemented in a recent project involved the following steps:

Inside the base.component file:

private subscriptions: any = {};

ngOnDestroy() {
  Object.keys(this.subscriptions).map(subscriptionKey => {
     this.subscriptions[subscriptionKey].unsubscribe();
  })
}

Then, in any component that extends this base component:

this.subscriptions.myService = this.myService.doStuff$.subscribe(......

Using this approach ensures that the subscription remains active until the component is destroyed.

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

How should one begin a new NativeScript-Vue project while implementing Typescript support in the most effective manner?

Is it possible to incorporate Typescript into Vue instance methods? I found guidance on the blog page of nativescript-vue.org. Whenever I initiate a new nativescript-vue project using vue init nativescript-vue/vue-cli-template <project-name>, some w ...

Encountering AJAX Error 0 with jQueryUI Autocomplete upon pressing enter key

Currently, I am facing an issue with a search box that utilizes the jqueryUI .autocomplete feature to retrieve data through AJAX for providing suggestions. The problem arises when a user presses the enter key before the AJAX call to the source completes, r ...

AngularJS ng-include nested form configuration

My application utilizes nested ng-includes. The outer include is a login screen for one application while the inner ng-include includes two templates. The login screen is designed in two steps where the email is checked first and then the password entered. ...

JavaScript Autocomplete - retrieving the value of a label

I am looking for a way to utilize the autocomplete feature in my form box to extract a specific value from the displayed label. When a user selects an option, I want to include part of the label (client_id) in the form submission process. JS: <script ...

Tips for troubleshooting JSON sorting in Angular

I am currently troubleshooting column positions within my application and need to inspect the sorted column definition. After retrieving the column definition from my API, I proceed to sort them. However, I also want to log the sorted list/Array to my co ...

The guidelines specified in the root `.eslintrc.json` file of an NX workspace do not carry over to the project-level `.eslintrc.json` file

In my main .eslintrc.json file, I have set up some rules. This file contains: { "root": true, "ignorePatterns": ["**/*"], "plugins": ["@nrwl/nx", "react", "@typescript-eslint", &qu ...

javascript href function usage

There's an issue I'm facing when using a link click to update a database field and redirect to another page. Here's the code I have: <a href="#" onclick="<?php $sql="UPDATE MyDB.mytable SET Date = '".da ...

Modifying the state object in ReactJS: A step-by-step guide on updating values

Below my query and explanation, you will find all the code. I am currently attempting to update the state in a grandparent class. This is necessary due to file-related reasons, and I am using Material-UI for text boxes. Additionally, I am implementing Red ...

What is the best way to display label information on a Google line chart?

line graph column graph My graph continuously calls the Controller to fetch recent data from the Database. There are two lines on the graph, and I would like to display the names of each line (column) such as red=counts of something // brown=counts of so ...

Why is my Feed2JS RSS feed functional locally but not operational after deployment on GitHub Pages?

I've been using feedtojs.org to display my Medium blog posts on my GitHub Pages website. Strangely enough, it works perfectly fine on my local server but not on the actual domain. The RSS feed is valid. Here's how it appears on my local server: ...

The beauty of crafting intricate forms with Angular's reactive nested

In my Angular project, I am exploring the concept of nesting multiple reactive forms within different components. For instance, I have a component called NameDescComponent that includes a form with two inputs - one for name and one for description, along ...

Button that vanishes when clicked, using PHP and HTML

I am in the process of creating an online store and I am seeking to implement a button that directs users to a new page, but then disappears permanently from all pages within the store. Here is the code snippet I have developed so far: <input class="b ...

Using base64 encoding and setting the binary type for Websockets

I'm feeling a bit lost. I've been working on understanding the mechanics of Websockets by setting up a server in node.js and a client-side application. One thing that's really puzzling me is how Websockets handle data transmission. Should da ...

Obtaining an identification using JQuery for content that is constantly changing

I am currently developing dynamic content tabs using PHP, with one of the objects being a datatable within the tab. In order to define the ID via PHP, I use the following code: PHP: echo '<table class="table table-striped table-bordered table-hov ...

What is the most efficient method for transferring Flask variables to Vue?

I am currently developing a visualization application using a flask server and vue.js for the front end. Other discussions on this topic explore how to avoid conflicts between vue.js and flask variable syntax, as shown here. In my scenario, I'm inte ...

Is there a way to include an image in a serialized file?

What is the method to include image_form into form in Django? form - form.serialize() image_form - image $('#id_submit').click(function(e) { e.preventDefault(); var form = $('form').serialize(); image_form = $("#id_image")[0].f ...

When using a React Router path variable along with other paths, React may have difficulty distinguishing between them

Setting up react router in my project to differentiate between user username variables and other paths is proving challenging. For instance: baseUrl/admin baseUrl/daniel Currently, React is unable to distinguish between the two. My intention is to query ...

Exploring the use of Jest for testing delete actions with Redux

I've been working on testing my React + Redux application, specifically trying to figure out how to test my reducer that removes an object from the global state with a click. Here's the code for my reducer: const PeopleReducer = (state:any = init ...

Next.js React Server Components Problem - "ReactServerComponentsIssue"

Currently grappling with an issue while implementing React Server Components in my Next.js project. The specific error message I'm facing is as follows: Failed to compile ./src\app\components\projects\slider.js ReactServerComponent ...

What's the best way to decrypt a string in NodeJS?

In the midst of my NodeJS & MongoDB project, I've encountered a requirement to encrypt the content of articles before they are published. The catch is that the encrypted content should only be displayed if the correct key-codes are entered. For ...