Search code examples
angularrxjs6behaviorsubject

Pass BehaviorSubject value as parameter


I have an Angular component with a BehaviourSubject initialized to the current month:

textLabel: string;

private monthIndex$: BehaviorSubject<number>;

private MONTHS = [
"Gennaio",
"Febbraio",
"Marzo",
"Aprile",
"Maggio",
"Giugno",
"Luglio",
"Agosto",
"Settembre",
"Ottobre",
"Novembre",
"Dicembre"
];

constructor(private listService: ShoppingListService) {}

ngOnInit() {
  this.monthIndex$ = new BehaviorSubject<number>(new Date().getMonth());

  // Like this it does not display the label
  this.setMonthLabel(this.monthIndex$.value); 

  this.model$ = this.monthIndex$.pipe(
    switchMap((monthValue: number) => {
      //    this.setMonthLabel(monthValue);  // This way it works!
      return this.listService.getHistoryChartModel(monthValue);
    }),
    takeUntil(this.destroy$)
  );
}

private setMonthLabel(monthIndex: number) {
  this.textLabel = this.MONTHS[monthIndex];
}

setMonth(direction: number) {
  let monthIndex = this.monthIndex$.getValue();
  monthIndex += direction;
  monthIndex =
  monthIndex < 0 ? 0 : monthIndex > 11 ? 11 : monthIndex;

  this.monthIndex$.next(monthIndex);
  this.setMonthLabel(monthIndex);
}

And the template:

<div class="view-sel-container">
  <button
   color="primary" mat-icon-button
   (click)="setMonth(-1)"
   [disabled]="monthIndex === 0">
  <i class="material-icons">
    keyboard_arrow_left
  </i>
 </button>

 <span class="label-text" *ngIf="textLabel">{{ textLabel }}</span>

 <button
  color="primary" mat-icon-button
  (click)="setMonth(1)"
  [disabled]="monthIndex === 11">
  <i class="material-icons">
    keyboard_arrow_right
  </i>
 </button>

Is it a timing reason why by passing the BehavourSubject value to the method this.setMonthLabel(this.monthIndex$.value), the label is not displayed in the template?

UPDATE

The solution provided by Deborah Kurata using get/set instead of BehaviourSubject is the best way to go. I leave the original question/code open as I still do not get why the code does now work by passing the behaviourSubject value as parameter.


Solution

  • Consider going with something like this that does not require a Subject or BehaviorSubject:

    Component

    import { Component } from '@angular/core';
    import { Observable } from 'rxjs';
    
    @Component({
      templateUrl: './history-chart.component.html'
    })
    export class HistorChartComponent {
      textLabel: string;
      model$: Observable<any>;
    
      private _monthIndex: number;
      get monthIndex(): number {
        return this._monthIndex;
      }
      set monthIndex(value: number) {
        console.log("setter called with: " + value);
        // Set the label
        this.setMonthLabel(value);
        // Get the data
        this.getMonthData(value);
        this._monthIndex = value;
      }
    
      private MONTHS = [
        "Gennaio",
        "Febbraio",
        "Marzo"
      ];
    
      constructor() { }
    
      ngOnInit() {
        // This calls the setter
        this.monthIndex = new Date().getMonth();
      }
    
      // Increment or decrement the month index
      // This calls the setter
      setMonth(value: number) {
        this.monthIndex += value;
      }
    
      private setMonthLabel(monthIndex: number) {
        this.textLabel = this.MONTHS[monthIndex];
      }
    
      private getMonthData(monthIndex: number): void {
        // Commented out because I don't have the service code
        //this.model$ = this.listService.getHistoryChartModel(monthIndex);
    
        // Faking out the call to the service
        this.model$ = of(
          { id: 1, value: "some data for month : " +  this.MONTHS[monthIndex] },
        );
      }
    }
    

    The setter is automatically called each time the user changes the value OR when the value is changed in code. So the setter is a good place to execute any code that needs to respond to the change.

    With the code above, the data for the month is retrieved on ngOnInt AND every time the user clicks either of the buttons. If you are not seeing this behavior with the provided stackblitz, please let me know.

    Template

    <div class="view-sel-container">
        <button
       color="primary" mat-icon-button
       (click)="setMonth(-1)"
       [disabled]="monthIndex === 0">
      <i class="material-icons">
        keyboard_arrow_left
      </i>
     </button>
    
     <span class="label-text" *ngIf="textLabel">{{ textLabel }}</span>
    
     <button
      color="primary" mat-icon-button
      (click)="setMonth(1)"
      [disabled]="monthIndex === 11">
      <i class="material-icons">
        keyboard_arrow_right
      </i>
     </button>
    
    <div>
    <span class="label-text" *ngIf="textLabel">
     {{textLabel}}
    </span>
    </div>
    <div *ngIf="(model$ | async) as model">
      <div>
      {{ model.value }}
      </div>
    </div>
    

    Here is the associated stackblitz: https://stackblitz.com/edit/angular-bpusk2