【问题标题】:How to Ensure a BehaviorSubject always has a value?如何确保 BehaviorSubject 始终具有值?
【发布时间】:2021-09-11 13:59:46
【问题描述】:

我正在使用 Angular 编写此应用程序,并且正在使用解析器。

一旦用户加载给定页面,就会有一个 API-Endpoint 将被解析器命中,并且需要传递的参数之一是用户所在的国家/地区。为了获取该信息,我使用了 ip-api.com API。

这是我的问题。因为我使用的是 Observables,所以在请求发出之前,我无法按时获取国家/地区位置。我需要先知道用户所在的国家,然后再发送请求。

我的想法是创建一个注入root 的服务,以便在应用程序启动时,它的构造函数立即执行一个方法来获取用户位置数据。然后,我将该数据存储在 BehaviorSubject 上,以便以后需要它的任何类都可以访问它。

如何确保在任何请求发出之前我始终拥有所需的数据?

如果用户来自主页面,一切正常,但如果用户直接进入辅助页面,则会出现错误。

这是我得到的:

助手服务

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { take } from 'rxjs/operators';

@Injectable( { providedIn: 'root' } )
export class HelperService {
    public showRightDrawer = new BehaviorSubject<boolean>(false);
    public requestCountry = new BehaviorSubject<string>(undefined);

    constructor(private http: HttpClient) {
        this.detectUserLocation();
    }

    detectUserLocation(): void {
        const url = 'http://ip-api.com/json';
        this.http.get(url).pipe(take(1)).subscribe((c: any) => {
            const cc = c.countryCode;
            this.requestCountry.next(cc);
            return cc;
        });
    }
}

访问我的内部 API 的服务

import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { SERVER_URL } from 'src/environments/environment';
import { HelperService } from '../common/services/helper.service';
import { Roaster } from './roaster.interface';

@Injectable()
export class RoasterService {
    private endPoint: string = SERVER_URL;
    private country: string;
    private httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json',
            responseType: 'text'
        })
    };

    constructor(private httpService: HttpClient, private helper: HelperService) {
        helper.requestCountry.subscribe((c) => this.country = c);
    }

    getRoasters(page?: number, limit?: number): Observable<Roaster[]> {
        const reqLimit = limit || 15;
        const reqPage = page || 0;
        return this.httpService
          .get<Roaster[]>(`${this.endPoint}roaster?limit=${reqLimit}&page=${reqPage}&country=${this.country.toLowerCase()}`, this.httpOptions )
          .pipe(take(1));
    }
}

【问题讨论】:

    标签: angular rxjs angular2-observables


    【解决方案1】:

    一般来说,最好不要订阅您的服务。 Observables 本质上是惰性的,在服务的构造函数中订阅会导致数据被获取,无论消费者是否订阅它。

    您可以在 HelperService 中将您的数据作为可观察对象公开,而无需像这样订阅:

    export class HelperService {
    
        constructor(private http: HttpClient) { }
    
        private url = 'http://ip-api.com/json';
    
        public countryCode$ = this.http.get(this.url).pipe(
            map(response => response.countryCode),
            shareReplay()
        );
    }
    

    您看到我们使用map 将响应转换为相关属性。我们使用 -shareReplay 以便多个订阅者共享同一个订阅并接收已经收到的值。现在,消费者可以订阅countryCode$ 以在需要时获取价值,但服务并没有发起数据流,消费者才是。

    现在,在您的 RoasterService 中,诀窍是返回一个首先引用 countryCode$ 的可观察对象,然后使用该数据进行 http 调用:

    export class RoasterService {
    
        private endPoint: string = SERVER_URL;
    
        constructor(private httpService: HttpClient, private helper: HelperService) { }
    
        getRoasters(page?: number, limit?: number): Observable<Roaster[]> {
            const reqLimit = limit || 15;
            const reqPage = page || 0;
    
            return this.helper.countryCode$.pipe(
                switchMap(cc => this.httpService.get<Roaster[]>(... use cc in here ...))
            );
        }
    
    }
    

    您可以看到我们将getRoasters() 方法的响应定义为以helpers.countryCode$ 开头,然后我们将结果通过管道传输到switchMap,这将在内部订阅我们的http.get() observable(当getRoasters 是订阅)并发出响应。

    【讨论】:

    • 对于问题的标题,您可以使用 ReplaySubject(1) 代替行为主题。对于行为主题,它将发出一个默认值。在你的情况下,这个默认值没有意义,所以最好不要发出,直到你有一个“真正的价值”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    • 2021-12-06
    • 2020-03-08
    • 1970-01-01
    相关资源
    最近更新 更多