获取 Observable 当前值的简单方法(从 BehaviorSubject 获取)

Simple way to get current value of an Observable (Obtained from a BehaviorSubject)

这道题看起来有点长,不过应该是一道很基础的rxjs题。如果可以,请帮忙。

我正在编写一个 Angular 7 应用程序,其中包含如下服务、组件和模板,我想以一种简单的方式访问可观察对象的最后一个值。

SoundMeterService

在服务中,我有一个私有的 BehaviorSubject,我用它来使用 'next' 发出值。 我使用 .asObservable() 将此 BehaviorSubject 公开给消费者,以防止消费者执行 .next() 并发出新值。 在服务中,我使用 .value.

访问 listeningStatusSubject 的当前值
export type SoundMeterStatus    = 'noResults' | 'isRunning' | 'hasResults';

export class SoundMeterService {
    private listeningStatusSubject: BehaviorSubject<SoundMeterStatus> = new BehaviorSubject<SoundMeterStatus>('noResults');
    get listeningStatus()   { return this.listeningStatusSubject.asObservable(); }

    // somewhere in the code I emit a different value
    someFunc() {
        if (this.listeningStatusSubject.value === 'noResults' )
            this.listeningStatusSubject.next('isRunning');
    }
}

SoundMeterComponent

遵循 Observable 的最佳实践,在组件中,我使用直接访问服务的 Observable(通过 listeningStatus getter),以便在模板中自动订阅和取消订阅(见下文)。

export class SoundMeterComponent {
    serviceListeningStatus$ = this.sms.listeningStatus;

    constructor(
        private sms: SoundMeterService
    ) {}

    ngOnInit() {
        // I want to check the value of listeningStatus in a simple way without pulling the value out and saving it locally in SoundMeterComponent
    }
}

SoundMeterTemplate

我正在使用 Angular 的异步管道,这样它会在组件被销毁时自动订阅和取消订阅可观察对象(以避免提到的手动订阅 here

<div *ngIf="(serviceListeningStatus | async) === 'noResults' ">
    <span>some text</span>
</div>

我的问题

这种方法非常有效。但是我想在 ngOnInit() 中以一种简单的方式检查 listeningStatus 的值,而不是将值提取出来并将其本地保存在 SoundMeterComponent 中,并且不公开 BehaviorSubject listeningStatusSubject 本身以使用 .值,如下所述:

1) 从服务中提取值并将其保存在本地 SoundMeterComponent 中:

SoundMeterComponent

export class SoundMeterComponent {
        serviceListeningStatus$ = this.sms.listeningStatus;
        private serviceListeningStatus;

        constructor(
            private sms: SoundMeterService
        ) {}

        ngOnInit() {
            // Pulling the value from the service:
            this.sms.listeningStatus.subscribe( status => this.serviceListeningStatus = status ); // I'm trying to avoid this

            if (this.serviceListeningStatus === 'noResults' ) {
                displayNoResultsMessage();
            }
        }
    }

2) 公开 BehaviorSubject 本身并使用 .value 检查其值,如下所示:

SoundMeterService

export class SoundMeterService {
    private listeningStatus: BehaviorSubject<SoundMeterStatus> = new BehaviorSubject<SoundMeterStatus>('noResults');
    get listeningStatus()   { return this.listeningStatus; }

}

SoundMeterComponent

export class SoundMeterComponent {
    serviceListeningStatus = this.sms.listeningStatus;

    constructor(
        private sms: SoundMeterService
    ) {}

    ngOnInit() {
        if (this.sms.listeningStatus === 'noResults' ) {
            displayNoResultsMessage();
        }
    }
}

SoundMeterTemplate

<div *ngIf="(serviceListeningStatus.asObservable() | async) === 'noResults' ">
    <span>some text</span>
</div>

请帮助我了解哪种方法更好,同时保持代码简洁明了。我知道有很多关于如何从像 Simple way to get the current value of a BehaviorSubject with rxjs5 这样的 BehaviorSubject 获取值的问题如何获取 RxJS Subject 或 Observable 的当前值? ,但请注意,这是一个不同的问题。 谢谢!

始终可以将 async/await 与 ngOnInit 结合使用并将可观察对象转换为 Promise,以提高可读性:

async ngOnInit = () => {
  const currentValue = await this.sms.listeningStatus.pipe(take(1)).toPromise();

  // do stuff with currentValue here
}

(注意:在将热可观察对象转换为承诺时始终使用 take(1) - 否则它们将永远无法解决,因为您需要在承诺解决之前完成可观察对象)。

恐怕您必须订阅 this.sms.listeningStatus,这就是 observables 的工作原理。如果您想坚持使用真正的异步数据处理方法,则没有解决方法。 您唯一可以做的就是将订阅移动到其他地方(例如使用异步管道移动到模板)

据我所知,我首先会质疑 BehaviourSubject 的用法。 RxJs,尽管它很棒,但并不适合所有用例,您的可能就是其中之一。 .value 的用法表示该 imo。

SoundMeterService 中使用一个简单的 non Observable 变量来保持状态可能会解决你所有的问题。

如果您不想这样做,我的建议是尝试以下操作: 在您的流程中有几个地方您想检查 listeningStatus 和 您可以选择使用 tap 运算符并在一个地方对所有选项(不仅 'noResults')执行副作用?

示例:

export class SoundMeterComponent {
    serviceListeningStatus$ = this.sms.listeningStatus.pipe(
        tap((status) => {
            if (value === 'noResults') {
                displayNoResultsMessage();
            }
            if (value === 'xxx') {
                doXXXStuff();
            }
        })
    );

    constructor(private sms: SoundMeterService) {}

    ngOnInit() {}
}

然后模板订阅 serviceListeningStatus$:

<div *ngIf="(serviceListeningStatus$ | async) === 'noResults' ">
    <span>some text</span>
</div>

我认为你的问题对于想要做 RxJs 的应用程序来说是一个很好的例子,因为它很棒,但是由于 Angular 框架中缺乏对反应式方法的全面支持,你不能完全反应。我正在考虑 ngOnInit 或用户进行的点击事件。 如果你仍然想使用 RxJs,看看 ngx-template-stream or at this great proposal 可能会有所帮助。

干杯克里斯