Передать значение BehaviorSubject в качестве параметра

У меня есть компонент Angular с BehaviourSubject, инициализированным текущим месяцем:

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);
}

И шаблон:

<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?

ОБНОВЛЕНИЕ

Решение, предоставленное Деборой Курата с использованием get/set вместо BehaviourSubject, является лучшим способом. Я оставляю исходный вопрос/код открытым, так как до сих пор не понимаю, почему код теперь работает, передавая значение behaviorSubject в качестве параметра.


person Francesco    schedule 18.02.2019    source источник
comment
теперь, когда я смотрю на ваш код глубже, вместо switchMap вы должны использовать tap   -  person Sandra Willford    schedule 19.02.2019
comment
Кажется, что этот код можно легко выполнить с помощью простых свойств и без Subject или BehaviorSubject. ИМХО, Subject/BehaviorSubject используются намного слишком часто, когда достаточно простых свойств... что делает код намного более сложным, чем он должен быть.   -  person DeborahK    schedule 19.02.2019
comment
@Sandra: monthIndex$ изменяется с помощью шаблона (поскольку он показывает просмотры месяцев), поэтому пользователь может несколько раз быстро нажимать на следующий месяц, поэтому я хочу удовлетворить только последний запрос. Я обновил вопрос, изменив метод monthIndex$.   -  person Francesco    schedule 19.02.2019
comment
Вот простой пример без Subject/BehaviorSubject: stackblitz.com/edit/angular-bpusk2   -  person DeborahK    schedule 19.02.2019
comment
Спасибо Деборе за пример, я уже изменил textLabel на просто строку. Однако, поскольку значение monthIndex$ может измениться с помощью взаимодействия с шаблоном (пользователь может выбрать следующий или предыдущий месяц), я подумал, что мне нужен BehaviourSubject, чтобы запрашивать новые данные из службы в соответствии с фактическим значением monthIndex$.   -  person Francesco    schedule 19.02.2019
comment
Я только что обновил stackblitz, чтобы включить эту дополнительную функцию определяемого пользователем индекса месяца. Вы можете использовать геттер/сеттер как гораздо более простой способ обработки изменений, чем BehaviorSubject   -  person DeborahK    schedule 19.02.2019


Ответы (2)


Подумайте о том, чтобы использовать что-то подобное, что не требует Subject или BehaviorSubject:

Компонент

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] },
    );
  }
}

Сеттер вызывается автоматически каждый раз, когда пользователь изменяет значение ИЛИ когда значение изменяется в коде. Таким образом, сеттер — это хорошее место для выполнения любого кода, который должен реагировать на изменение.

С помощью приведенного выше кода данные за месяц извлекаются из ngOnInt И каждый раз, когда пользователь нажимает любую из кнопок. Если вы не видите такого поведения с предоставленным stackblitz, сообщите мне об этом.

Шаблон

<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>

Вот связанный stackblitz: https://stackblitz.com/edit/angular-bpusk2

person DeborahK    schedule 18.02.2019
comment
Я обновил свой вопрос, добавив вызов из шаблона для установки нового месяца. Если я использую set/get для месячного индекса и вызываю с ним метод this.listService.getHistoryChartModel (он мне нужен в Init, а не в методе SetLabel), он не запускает новый запрос. Поэтому я изначально использовал BehaviourSubject. Я все еще что-то упускаю? - person Francesco; 19.02.2019
comment
Я использую этот подход в своих курсах, и у меня есть клиенты, которые использовали подход getter/setter в своих бизнес-приложениях. Если это не работает для вас, пожалуйста, разветвите и обновите мой stackblitz, чтобы показать, где это не работает. - person DeborahK; 19.02.2019
comment
Еще раз спасибо, я проверю. Ради интереса, знаете ли вы, почему передача значения BehavourSubject в setMonthLabel не сработала, хотя внутри switchMap это сработало? Это был мой оригинальный вопрос (даже если вы совершенно правы, пытаясь избежать BehaviourSubject, если он не нужен) - person Francesco; 19.02.2019
comment
Это прекрасно работает. Очень интересно посмотреть на это (сейчас) под другим углом и увидеть, насколько простым может быть код! - person Francesco; 19.02.2019
comment
Рад слышать! Я просматриваю ваш исходный код, пытаясь ответить на ваш исходный вопрос ... но я не могу заставить этот код работать: const monthIndex += direction; Если код объявляет это значение здесь, а затем либо добавляет, либо вычитает его, не будет ли это < i>всегда быть 1 или -1? Не тот monthIndex? - person DeborahK; 19.02.2019
comment
Отсутствует одна строка (только что обновлена). Я инициализирую monthIndex (как значение BehaviorSubject) текущим месяцем, а затем добавляю или вычитаю 1 из шаблона, чтобы переключиться на следующий/предыдущий месяц соответственно. - person Francesco; 19.02.2019
comment
Кстати, код switchMap, кажется, работает, если вы удалите код takeUntil. Вы можете найти обсуждение того, почему switchMap и takeUntil ведут себя именно так, здесь: blog.strongbrew.io/: Because the term$ observable is keeping track of the last value, the takeUntil operator will always have a value, resulting in the fact that every XHR call gets aborted immediately. - person DeborahK; 19.02.2019
comment
Давайте продолжим обсуждение в чате. - person Francesco; 19.02.2019

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. Вы должны использовать Tap для всего, что является побочным эффектом.
  2. Я не знаю, зачем вам нужно использовать this.setMonthLabel(this.monthIndex$.value), если у вас уже есть значение в вашем канале?
  3. Что делает this.setMonthLabel?
person Sandra Willford    schedule 18.02.2019