Even after the component is destroyed, the subscription to the service observable continues to emit

I'm facing an issue with an observable in my service. The provided code below demonstrates this:

@Injectable({
  providedIn: 'root'
})
export class MyService {
  public globalVariable: BehaviorSubject<string> = new BehaviorSubject('');
}

A feature component is involved, called:

export class ComponentA implements OnInit {
   constructor(public myService : MyService ) {
      this.myService.globalVariable.next('newValue');
   }

   ngOnInit() {
      this.myService.globalVariable.subscribe(_ => console.log('=> hello'));
   }
}

The App Module setup looks like this:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ComponentAModule,
    ComponentBModule,
    AppRoutingModule
  ],
  providers: [MyService],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Here is the structure of the project:

app-module.ts
app-routing.module.ts
-components
-- componentA
--- componentA.module.ts
--- componentA-routing.module.ts
--- componentA.component.ts
--- componentA.component.html
-- componentB
--- componentB.module.ts
--- componentB-routing.module.ts
--- componentB.component.ts
--- componentB.component.html

Upon navigating to componentA, I noticed the following behavior:

=> hello
=> hello

At this point, everything works as expected. The initial subscribe is triggered followed by the change in globalVariable initiated by componentA's constructor.

However, when switching to componentB and then back to componentA, the output changes to:

=> hello
=> hello
=> hello

Every time I navigate back to componentA, a new line is added. It seems like a new instance of MyService is created or the subscribe isn't being destroyed upon leaving?

Note: Lazy loading is not utilized in this scenario.

Answer №1

It is important to manually destroy subscriptions that are not managed by Angular itself, especially when dealing with httpClient subscriptions. When using the | async pipe, Angular automatically handles unsubscribing.

To properly unsubscribe, call yourSubscription.unsubscribe() within the ngOnDestroy() lifecycle hook of your component.

A good practice is to create a BaseComponent that takes care of unsubscribing for you. Extend this base class in all your components and wrap each subscription call in super.addSubscription().

import { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

/**
 * This class handles the subscribing and unsubscribing of subscriptions to prevent memory leaks
 * and can be inherited by components
 *
 * @export
 */
export abstract class BaseComponent implements OnDestroy {

private subscriptions: Subscription[] = new Array<Subscription>();

ngOnDestroy() {
    this.removeSubscriptions();
}

/**
 * Adds a subscription so it can be unsubscribed in ngOnDestroy
 *
 * @param subscription The subscription to add
 * @memberof BaseComponent
 */
protected addSubscription(subscription: Subscription) {
    this.subscriptions.push(subscription);
}

/**
 * Unsubscribes from any open subscriptions in the array during ngOnDestroy
 *
 * @memberof AbstractBaseComponent
 */
private removeSubscriptions() {
    for (let subscription of this.subscriptions) {
        subscription.unsubscribe();
    }
}
}

UPDATE

Follow these steps for your ngOnInit(), assuming you are utilizing the provided base class:

export class ComponentA extends BaseComponent implements OnInit {
    constructor(public myService : MyService ) {
       this.myService.globalVariable.next('newValue');
    }
    ngOnInit() {
       super.addSubscription(
           this.myService.globalVariable.subscribe(_ => console.log('=> hello'))
       )
    }
}

Answer №2

To properly unsubscribe, remember to include unsubscribe within the ngOnDestroy lifecycle hook:

import { Subscription } from 'rxjs';

globalSubscription$: Subscription;

ngOnInit() {
  this.globalSubscription$ = this.myService.globalVariable.subscribe(_ => console.log('=> hello'));
}

ngOnDestroy() {
  this.globalSubscription$.unsubscribe();
}

Answer №3

If you're looking to avoid using async pipe and instead want to subscribe manually, one option is to utilize the RxJs operator takeWhile. Take a look at the code snippet provided below for an example...

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs';
import { takeWhile, map } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit, OnDestroy {
  name = 'Angular';
  isActive: boolean;
  // received from service. Does not need initialization here
  thingToSubscribeTo:Observable<any> = new Observable<any>();

  ngOnInit() {
    this.isActive = true;
    // could be replaced with a call to a service followed by piping
    this.thingToSubscribeTo.pipe(
      map(res => {
        // handle subscription logic here
      }),
      takeWhile(() => this.isActive)
    );

  }

  ngOnDestroy() {
    this.isActive = false;
  }
}

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

What is the importance of using ChangeDetectorRef.detectChanges() in Angular when integrating with Stripe?

Currently learning about integrating stripe elements with Angular and I'm intrigued by the use of the onChange method that calls detectChanges() at the end. The onChange function acts as an event listener for the stripe card, checking for errors upon ...

Utilize Hostbinding in Angular to Inject Style Declarations

Is there a way to efficiently inject multiple style declarations into a component using the @HostBinding decorator? I've been attempting the following: @HostBinding('style') get style(): CSSStyleDeclaration { return { background: &apo ...

When trying to run the npm start command, an error occurs referencing type

I am facing difficulties running my project locally due to broken dependencies, and I'm struggling to find a solution. When attempting to install the node modules, I encountered errors, so I made changes to the following dependencies: "codelyze ...

How can I retrieve the current table instance in NGX-Datatables?

I have a component that includes a table. This table receives RowData from another component through the use of @Input. How can I access the current instance of this table? Here is a snippet of my HTML: <ngx-datatable class="material" ...

Encountering NaN in the DOM while attempting to interpolate values from an array using ngFor

I am working with Angular 2 and TypeScript, but I am encountering NaN in the option tag. In my app.component.ts file: export class AppComponent { rooms = { type: [ 'Study room', 'Hall', 'Sports hall', ...

Tips for adding temporary text in filter input of Kendo UI Grid using Angular

I'm currently working with Kendo UI Grid in conjunction with Angular, and I am struggling to find a solution for adding text or a placeholder in filter inputs using Typescript. Within my code, I am utilizing the kendoGridFilterCellTemplate: <kend ...

Issue with Angular reactive forms when assigning values to the form inputs, causing type mismatch

I'm a beginner when it comes to reactive forms. I'm currently working on assigning form values (which are all string inputs) from my reactive form to a variable that is an object of strings. However, I am encountering the following error: "Type ...

Guide to refreshing filters once data is updated in PrimeNG tables?

Whenever I add new rows to the table, the display updates dynamically. However, any filters I have applied only reflect the initial data. https://i.stack.imgur.com/5Nuxe.png For example, if I use the "startsWith" filter on a column labeled "Title" with a ...

What is the best way to extract data from API console output using Angular?

Currently, I am deeply involved in a full stack project utilizing asp.net and angularjs. My goal is to extract output response from the Swagger API. You can view the code I've written for this purpose here: (https://i.stack.imgur.com/vLyIE.png) The A ...

The issue of Angular 6 view failing to display accurate data upon page load

In my Angular 6 application, I've implemented a login feature using Firebase's Google login. The interface includes a button labeled Login when the user is logged out, and changes to Logout with the current email address displayed when the user i ...

Update the data in Firebase, but revert it back to the original state after a few seconds with the use of "angularFire."

I'm currently working on updating data in the Firebase Realtime Database using Angular and AngularFire. The issue I'm facing is that even though the data changes successfully, it reverts back to the original data after a few seconds. Below is the ...

The issue arises when attempting to use the search feature in Ionic because friend.toLowerCase is not a valid function

I keep encountering an error message that says "friend.toLowerCase" is not a function when I use Ionic's search function. The unique aspect of my program is that instead of just a list of JSON items, I have a list with 5 properties per item, such as f ...

Each Tab in Ionic2 can have its own unique side menu that opens when selected

In my ionic2 app, I wanted to implement a unique side menu for each of my tabs. Here is what I attempted: I used the command ionic start appname tabs --v2 to create the initial structure. Next, I decided to turn both home.html and contact.html (generated ...

Leveraging the information retrieved from Promise.all calls

Using the service method below, I send two parallel queries to the server with Promise.all. The returned results are stored in the productCategoryData array, which is then logged to the console for verification. Service method public getProductCategoryDa ...

The information retrieved from the API is not appearing as expected within the Angular 9 ABP framework

I am facing an issue with populating data in my select control, which is located in the header child component. The data comes from an API, but for some reason, it is not displaying correctly. https://i.stack.imgur.com/6JMzn.png. ngOnInit() { thi ...

Parentheses are automatically wrapped around the implicit return of arrow functions

Currently, I am utilizing Visual Studio Code along with Prettier, and I have noticed that the function: (token: string) => this.token = token is being transformed into: (token: string) => (this.token = token) This modification seems to decrease r ...

Determining if an emitted event value has been altered in Angular 4

I am currently working on an Angular 4 project. One of the features I have implemented is a search component, where users can input a string. Upon submission of the value, I send this value from the SearchComponent to the DisplayComponent. The process of ...

Basic HTML Audio Player Featuring Several Customizable Variables

I have a unique API that manages music playback. Instead of playing audio in the browser, it is done through a Discord bot. Achievement Goal https://i.stack.imgur.com/w3WUJ.png Parameters: current: indicates the current position of the track (e.g. 2:3 ...

The Angular template driven forms are flagging as invalid despite the regExp being a match

My input looks like this: <div class="form-group"> <label for="power">Hero Power</label> <input [(ngModel)]="model.powerNumber" name="powerNumber" type="text" class="form-control" pattern="^[0-9]+$"id= ...

How can I dynamically generate multiple Reactive Forms from an array of names using ngFor in Angular?

I am in the process of developing an ID lookup form using Angular. My goal is to generate multiple formGroups within the same HTML file based on an array of values I have, all while keeping my code DRY (Don't Repeat Yourself). Each formGroup will be l ...