【问题标题】:How do I make a component wait for async data from service如何让组件等待来自服务的异步数据
【发布时间】:2022-08-01 21:13:57
【问题描述】:

一项服务正在获取 api 数据,每次 url 更改(路由器链接)时,我都需要在许多其他组件中保留并使用它,而无需再次调用 api。 Api 应该仅在服务运行时获取。

我需要让组件方法等到数据可用。

(为了便于阅读,实际代码减少到最低限度)

服务

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

  fetchData(): void {
    this.http.get(this.endpointUrl).subscribe((res)=>{
      this.data = res
    });
  }

  getData(selectedCategory): DataType {
    return this.data.find(cat => cat === selectedCategory)
  }

零件

  ngOnInit(): void {
    this.activatedRoute.paramMap.subscribe(
      (params: ParamMap) => {
        // I need params before calling service method
        // this method must wait until the \"data\" is available in service.
        this.selectedCategory = this.service.getData(params.category);
      }
    );
  }

    标签: javascript angular rxjs


    【解决方案1】:

    我建议使用异步管道(如果您需要显示数据)和 您应该避免嵌套订阅。

    对于页面刷新的数据持久性,您需要使用本地存储或会话存储。我不是这个的忠实粉丝,但我想这取决于你的要求。

    你可以在 BehaviourSubject 的帮助下管理你的状态,但是如果你想做更复杂的事情并保持你的代码干净,我会推荐使用像 @ngrx/component-store 这样的状态管理库。

    您的服务

    private readonly _state = newBehaviourSubject<Record<string, DataType>>(JSON.parse(localStore.getItem('state')) ?? {});
    readonly state$ = this._state.asObservable();
    
    
    getData(category: string) {
            return of(category).pipe(
                withLatestFrom(this.state$), // you don't want to do something like this.state.pipe(...) because you'll resubscribe to it multiple times because the implementation below
                switchMap(([key, data]) => {
                        if (key in data) return of(data[key]); // return the found category
    
                        return this.http.get(this.endpointUrl).pipe(
                            tap(res => {
                                const updatedData = { ...data, [key]: res };
                                this._state.next(updatedData); // update your state
                                localStorage.setItem('state', JSON.stringify(updatedData) // update local storage
                            })
                        );
                    }
                )
        }
    

    零件

    readonly selectedCategory$ = this.activatedRoute.paramMap.pipe(
       map(paramMap => paramMap.get('category')), // I assume that you expect a string
       filter(category => !!category) // it makes sure that the result is not null or undefined
       switchMap(category => this.service.getData(category)),
       catchError(error => {...}), // handle your errors
       filter(res => !!res) // if you want to make sure the response is not null
    )
    
    

    【讨论】:

    • 对于我的需求描述有误,我深表歉意。我已经对其进行了编辑,提到将在应用程序加载(页面刷新)时调用 api,但无论如何感谢您的解决方案。
    • 没关系。无论如何,上面的解决方案将在没有本地存储的情况下工作。只需将其从实现中删除并尝试一下。
    【解决方案2】:

    如果没有很多更改,就可以订阅fetchData(),它会返回一个可观察的传输请求数据。我们使用tap 来获得在服务中也设置this.data 的副作用。别忘了unsubscribe()!还要注意一些语法错误,我没有检查它们。建议为&lt;any&gt; 添加类型。

      fetchData(): Observable<any>{
       return this.http.get(this.endpointUrl).pipe(
          tap(res => this.data = res),
          catchError(console.log(err))
        )
      }
    
      ngOnInit(): void {
        this.activatedRoute.paramMap.subscribe(
          (params: ParamMap) => {
            this.myService.fetchData().subscribe(res => {
            this.selectedCategory = this.service.getData(params.category);
            // Or use
            // this.selectedCategory = this.res.find(cat => cat === params.category)
             });  
          }
        );
      }
    

    订阅服务

    虽然不建议订阅服务(尤其是onRootService),因为服务会发出请求每次url 更改,但我想这就是您想要的。所以要警告。

    我们订阅构造函数中的参数更改。服务将在每次路线更改时请求新数据。查询的数据将使用BehaviourSubject 发出,该BehaviourSubject 保留和更新数据。组件可以订阅BehaviourSubject,而不是使用| async 管道或手动订阅。

    服务:

    let data = new BehaviorSubject(null); 
    
    constructor() {
        this.activatedRoute.paramMap.subscribe(
          (params: ParamMap) => {
            this.fetchData().subscribe(res => {
               this.data.next(res);
             });  
          }
        );
      }
    

    【讨论】:

    • 我已经尝试过了,但如前所述,我正在尝试找到一种不必在每次 url 更改时都调用 api 的方法。 Api 应仅在应用程序加载(页面刷新)时调用,这应保持服务中的值,该值应由 routerLink url 更改上的组件访问。
    • 因此,请订阅服务中的参数更改并发出请求的数据。我为此添加了一个示例。
    • 此订阅仅适用于页面重新加载,不适用于 RouterLink url 更改。
    • 例如router.events.filter(event =&gt; event instanceof NavigationEnd).subscribe() - 我从你的例子中取了activatedRoute
    猜你喜欢
    • 1970-01-01
    • 2018-08-12
    • 2019-06-14
    • 2019-11-12
    • 2017-05-14
    • 1970-01-01
    • 2016-03-16
    • 1970-01-01
    • 2018-10-22
    相关资源
    最近更新 更多