Francesco
Francesco

Reputation: 10870

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.

Upvotes: 4

Views: 3361

Answers (2)

DeborahK
DeborahK

Reputation: 60626

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

Upvotes: 2

Sandra Willford
Sandra Willford

Reputation: 3789

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(
    tap(monthValue => {
      this.setMonthLabel(monthValue);
      return this.listService.getHistoryChartModel(monthValue);
    }),
    takeUntil(this.destroy$)
  );
  1. You should use tap for anything that is a side-effect.
  2. I dont know why you would need to use this.setMonthLabel(this.monthIndex$.value) when you have the value already in your pipe?
  3. What does this.setMonthLabel do?

Upvotes: 0

Related Questions