2

I am having issues with a front-end component not rendering updates to a BehaviorSubject and I'm hoping someone can point out where my error is coming from. I have read many other questions about BehaviorSubject issues, but so far I cant get a solution. I'm using Angular 8 with Ionic 4.

Here is the front end experince: When I navigate to the component the first time, it will not show any projectDetails. Navigating to the componenet the second time, it shows me the projectDetails that should have been displayed the first time. This 'lag' persists for subsequent navigations

I understand that this must mean my call to .next() is not getting reflected in the component until it is re-initialized.

I know that this.s.getProject(this.projectId); is working correctly when the component loads because the console.log('service fetch complete>>>>>>',this.project.getValue()['name']); from the service reports the appropriate 'project' per the current navigation. But I'm struggling to understand why this data is only propagating to the component the next time it is opened.

I'm starting to suspect that I am missing something on the layout side...

The Service

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


@Injectable({
  providedIn: 'root'
})

export class myService {

  private API_URL = 'http://xxx.xxxxx.com/endpoint/';

  private project: BehaviorSubject<any> = new BehaviorSubject({});
  public readonly projectObservable: Observable<Object> = this.project.asObservable();

  constructor(private http:HttpClient) { }

  getProject(projectID:string ){
    console.log('service fetching from>>>>>>>',this.API_URL+projectID);
    this.http.get(this.API_URL+projectID).subscribe(
      (res) => {
        this.project.next(res);
        console.log('service fetch complete>>>>>>',this.project.getValue()['name']);
      }
    );
  }
}

The Component

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { myService } from '../../services/myService.service';


@Component({
  selector: 'app-layout',
  templateUrl: './layout.component.html',
  styleUrls: ['./layout.component.scss'],
})
export class LayoutComponent implements OnInit, OnDestroy {
  // Route Paramaters
  private projectTitle: string; 
  private projectId: string;
  // Content Object
  private projectDetails;

  constructor(private route: ActivatedRoute, private s: myService) {
    this.route.params.subscribe(routeParams => console.log("layout.component params", routeParams));
  }

   ngOnInit() {
    this.projectTitle = this.route.snapshot.params.projectTitle;
    this.projectId = this.route.snapshot.params.projectId;

    this.s.getProject(this.projectId);
    this.s.projectObservable.subscribe(prj => this.projectDetails = prj);  
  }

  ngOnDestroy() {
    console.log('hello - layout component destroyed');
  }

}

The Layout

<ion-card>

  <ion-card-header>
    <ion-card-subtitle>{{projectDetails.type}}</ion-card-subtitle>
    <ion-card-title>{{ projectTitle }}</ion-card-title>
  </ion-card-header>

  <ion-card-content>
    <div markdown ngPreserveWhitespaces>
      {{projectDetails.Narrative}}
    </div>  
  </ion-card-content>

</ion-card>

Any help is much appreciated!

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • Well, I don't see where is the problem, but I prefer to use async pipe to render an observable. It is nice since your component will handle unsubscription (your code doesn't do it !). Async pipe can be done like: *ngIf="projectDetails$ | async as projectdetails" in template.. make sure that the projectDetails$ contains the observable itself... – gaborp Oct 16 '20 at 20:54

2 Answers2

0

An idea: perhaps the route subscription is not triggered before your ngOnInit() is called, so the route does not yet contain the data you seek. You could subscribe to your route while you ar in ngOnInit() and within that subscription execute the logic that is now in your ngOnInit()

Dennis
  • 371
  • 1
  • 9
0

The Problem

The problem is in the below line this.projectId = this.route.snapshot.params.projectId;. When you visit the page the first time, angular will load the component. When you visit the page the second time, angular notes that there has been no change in the component and reuses the already loaded component hence the behaviour

Solution

You need a way to trigger change detection when the router params changes. The easiest way is to simply use paramMap on an injected ActivatedRoute instance

In your ngOnInit life cycle hook replace your code with

  this.route.paramMap.pipe(
    map(params => params.get('projectTitle')),
    tap(projectTitle => this.projectTitle = projectTitle)
  ).subscribe()
  this.route.paramMap.pipe(
    map(params => params.get('projectId')),
    tap(projectId => this.projectId = projectId),
    tap(projectId => this.s.getProject(this.projectId))
  ).subscribe()
  this.s.projectObservable.subscribe(prj => this.projectDetails = prj);  
Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74