Search code examples
angularrxjsrxjs-observablesbehaviorsubject

Behaviour subject subscriptions not working when updating from lower components and called inside top level components


This issue is on an Angular 16 project. I am trying to pass a title that needs to be displayed inside the title bar from the bottom level/journey pages. The title bar is outside the router-outlet inside the app.component.html file.

app.component.html:

<app-title-bar></app-title-bar>
<router-outlet></router-outlet>

I have setup a behaviourSubject variable for title, in a service, named TitleService, and will be updating it from the journey pages, like user details page. The title service is inside a seperate library 'ui-components' and is imported into app.module.ts

app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { UiComponentsModule } from '@ui-components';


@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    AppRoutingModule,
    NgbModule,
    UiComponentsModule
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

ui-components.module.ts:


import { TitleBarComponent } from './title-bar/title-bar.component';
import { TitleService } from './services/title/title.service';

@NgModule({
    imports: [
       CommonModule,
       RouterModule,
       ...
       ...
       ...
    ],
    declarations: [
       ...
       ...
       ...
       TitleBarComponent
    ],
    exports: [
       ...
       ...
       ...
    ],
    providers: [TitleService],
})
export class UiComponentsModule {}

When I try to update the value from the user details page, even after being subscribed to the variable from the service, inside the titlebar, I am not getting the updated value inside the titlebar.

title.service.ts:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })

export class TitleService {
    private title = new BehaviorSubject<string | null | undefined>('');
    public title$: Observable<any> = this.title$.asObservable();

    constructor() {}

    updateTitle(value: string | any) {
        this.title.next(value);
    }

}

user-details.component.ts:

import { Component, OnInit } from '@angular/core';
import { UserService } from '../../../account.service';
import { TitleService } from '@ui-components';

@Component({
    selector: 'app-user-details',
    templateUrl: './user-details.component.html',
})
export class UserDetailsComponent implements OnInit {
    constructor(private titleService: TitleService) { 

    }

    
}

title-bar.component.ts:

import { Component, OnInit } from '@angular/core';
import { TitleService } from '@ui-components';

@Component({
    selector: 'app-title-bar',
    templateUrl: './title-bar.component.html',
})
export class TitleBarComponent implements OnInit, OnDestroy {

private unsubscribe: Subscription[] = [];

constructor(
        private titleService: TitleService,
    ) {}

    get pageTitle(): Observable<string> {
        return this.titleService.title$;
    }
}

title-bar.component.html:


<p>{{ pageTitle | async }}</p>

The data is being passed on from the user details page and is received at the title service. But its not being shown in the subscriptions inside the title bar component.

I need the data, that's being updated from any component into the service variable, to be available within the title bar component.


Solution

  • The issue might be that, TitleService is not acting as a singleton.

    Since the service is inside a library called ui-components, in your project and is set as native to it, this might prevent your service from being accessed in the same way, by other components or libraries other than your ui-components library.

    This means you could be making updates to your TitleService from within the library it is native to and get the updates properly. But if some other library tries to use your service, they would be served with a seperate instance of your service, and this instance might not have the changes, you made earlier, from within its native module.

    If that might be the case, then I would suggest removing the TitleService into UiComponentsModule 's providers array.

    import { TitleBarComponent } from './title-bar/title-bar.component';
    
    @NgModule({
        imports: [
           CommonModule,
           RouterModule,
           ...
           ...
           ...
        ],
        declarations: [
           ...
           ...
           ...
           TitleBarComponent
        ],
        exports: [
           ...
           ...
           ...
        ],
        providers: [],
    })
    export class UiComponentsModule {}
    

    As your TitleService already has @Injectable({ providedIn: 'root' }), the service will be accessible throughout the whole application as a singleton object that has all the changes made to it.

    Here is my reference: https://angular.io/guide/singleton-services#the-forroot-pattern