Dan Friedman
Dan Friedman

Reputation: 507

Failure to communicate events between components

I have got this class that acts as a mediator:

 export class ProgressBarService {

  // Observable string sources
  private progressAnnouncement = new Subject<number>();

  // Observable string streams
  progressAnnounced$ = this.progressAnnouncement.asObservable();

  constructor() { }

  AdministerProgress(progress: number) {
    this.progressAnnouncement.next(progress);
  }
}

Component A sends a message like this in ngOnInit:

  this.progressBarService.AdministerProgress(100);

But it never gets to the navigational component:

 constructor(private breakpointObserver: BreakpointObserver,
    private requestBuilder: RequestBuilder,
    private cdref: ChangeDetectorRef,
    private router: Router,
    private progressBarService: ProgressBarService ) {
    
  }

  ngOnInit(): void {
 
  }

  ngAfterContentChecked() {
    this.progressBarService.progressAnnounced$.subscribe(announcement => {
      console.log(announcement);
      this.progressBarValue = announcement;
      this.cdref.detectChanges();
    });
  }

I get no message for the console.log. Why is that? UPDATE I followed this explanation Behavior subject

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

@Injectable({
  providedIn: 'root'
})
export class ProgressBarService {

  // Observable string sources
  public progressAnnouncement$ = new BehaviorSubject(0);


  constructor() { }

  AdministerProgress(progress: number) {
    console.info('inside func',progress);
    this.progressAnnouncement$.next(progress);
  }
}

The navigation header of the page which loads first subscribes:

    ngAfterContentChecked() {
    this.progressBarService.progressAnnouncement$.subscribe(announcement => {
      console.info('Announcement', announcement);
      this.progressBarValue = announcement;
      this.cdref.detectChanges();
    });
  }

Then another component that loads much later emits values by using the AdministerProgress function, as such:

    this.progressBarService.AdministerProgress(100);

The header component never receives the 100 value. All it gets are many 0 values one after the other.

UPDATE:

Here is the shared class:

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

@Injectable({
  providedIn: 'root'
})
export class ProgressBarService {

  public progressAnnouncement$ = new BehaviorSubject(0);
  constructor() { }

  AdministerProgress(progress: number) {
    this.progressAnnouncement$.next(progress);
  }
}

Here is the singleton module that I created, as was explained in this link:

https://angular.io/guide/singleton-services#providing-a-singleton-service

    import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProgressBarService } from 'app/Services/Classes/ProgressBarServices/ProgressBarService';



@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ]
})
export class SingletonModule {

  constructor(@Optional() @SkipSelf() parentModule?: SingletonModule) {
    if (parentModule) {
      throw new Error(
        'SingletonModule is already loaded. Import it in the AppModule only');
    }
  }

  static forRoot(): ModuleWithProviders<SingletonModule> {
    return {
      ngModule: SingletonModule,
      providers: [
        ProgressBarService
      ]
    };
  }
}

This is the section of my app.module where I import that module:

  imports: [
CommonModule,
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatsharedModule,
HttpClientModule,
SharedModule,
PrimeNGModule,
LayoutModule,
SingletonModule.forRoot(),

Here is my main AppRoutingModule config file:

    import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { DashboardComponent } from './Dashboard/dashboard/dashboard.component';
import { PageNotFoundComponent } from './Errors/page-not-found/page-not-found.component';
import { AuthGuard } from './Services/Guards/auth.guard';


const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'authentication',
    loadChildren: () => import('./authentication/authentication.module').then(m => m.AuthenticationModule)
  },
  {
    path: 'accounting',
    loadChildren: () => import('./accounting/accounting.module').then(m => m.AccountingModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'users',
    loadChildren: () => import('./users/users.module').then(m => m.UsersModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'game-management',
    loadChildren: () => import('./game-management/game-management.module').then(m => m.GameManagementModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'operator',
    loadChildren: () => import('./operator-management/operator-management.module').then(m => m.OperatorManagementModule),
    canActivate: [AuthGuard]
  },
  {
    path: 'technical',
    loadChildren: () => import('./technical/technical.module').then(m => m.TechnicalModule),
    canActivate: [AuthGuard]
  },
  {
    path: '404',
    component: PageNotFoundComponent
  },
  {
    path: '',
    redirectTo: 'dashboard',
    pathMatch: 'full'
  },
  {
    path: '**',
    redirectTo: '404',
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes,
   {
   // enableTracing: true,// <-- debugging purposes only
    preloadingStrategy: PreloadAllModules
  }
  )],
  exports: [RouterModule]
})
export class AppRoutingModule { }

The other module dont have a trace of the service provided or the singleton module imported.

I believe that I miss some configuration.

Upvotes: 0

Views: 46

Answers (1)

Ethan Vu
Ethan Vu

Reputation: 2987

It's likely that the navigation component subscribe to the Observable after component A fire the data, which mean it miss the data.

The solution is to change progressAnnouncement from Subject to BehaviorSubject or ReplaySubject. Each of those two have slighly different use but the main different from Subject is they store the data inside and new subscribers always have the latest data (BehaviorSubject) or all the data emitted (ReplaySubject).

Update :

In case of layzy-loading, there change that both component using different instances of the same service, the most easy way is to mark the service as app-wide singleton using @Injectable({providedIn: 'root'}) pattern. You can read more about singleton service in Angular here.

Upvotes: 2

Related Questions