【问题标题】:Simple way to get current value of an Observable (Obtained from a BehaviorSubject)获取 Observable 当前值的简单方法(从 BehaviorSubject 获得)
【发布时间】:2020-01-18 10:24:24
【问题描述】:

这个问题看起来很长,但应该是一个很基础的rxjs问题。如果可以的话,请帮忙。

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

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

遵循可观察对象的最佳实践,在组件中,我使用直接访问服务的可观察对象(通过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 的异步管道,以便它在组件被销毁时自动订阅和取消订阅 observable(以避免here 提到的手动订阅)

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

我的问题

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

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 rxjs5How to get current value of RxJS Subject or Observable? 这样的 BehaviorSubject 获取值有很多问题,但请注意,这是一个不同的问题。 谢谢!

【问题讨论】:

  • 出现一个问题:我们真的要检查onInit 中的状态吗?你能确定在调用ngOninit 时结果已经确定了吗?否则,由于我们通过异步管道订阅模板,我们可以在组件中的 listeningStatus 可观察对象上使用 pipetap 运算符,并在其中执行 displayNoResultMessage() 副作用。
  • 原来它是一个 BehaviorSubject(即有一个初始值),所以是的,在 ngOnInit 中使用它是安全的。事实上,我在代码中的其他地方使用了if(this.serviceListeningStatus === ... ),我希望listeningStatus 已经被更改。如果我在代码中的其他3个地方使用它,你认为我还需要拉值并保存在本地吗?
  • 为什么不直接在 SoundMeterService 中公开当前状态(在行为主体上使用 .getValue() 返回具体值)?

标签: angular rxjs observable behaviorsubject


【解决方案1】:

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

SoundMeterService 中使用一个简单的 Observable 变量来保持状态,可以解决您的所有问题。

如果您不想这样做,我的建议是尝试以下方法: 您的流程中有几个地方要检查 listeningStatus 和 您可以选择在一个地方使用点击运算符并为所有选项(不仅是“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 或这个很棒的proposal 可能会有所帮助。

克里斯干杯

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      始终可以将 async/await 与 ngOnInit 结合使用并将 observable 转换为 Promise,从而为您提供一些可读性:

      async ngOnInit = () => {
        const currentValue = await this.sms.listeningStatus.pipe(take(1)).toPromise();
      
        // do stuff with currentValue here
      }
      

      (注意:在将 hot observable 转换为 Promise 时始终使用 take(1) - 否则它们将永远无法解析,因为您需要在 Promise 解析之前完成 observable)。

      【讨论】:

        猜你喜欢
        • 2016-12-11
        • 2020-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多