【问题标题】:How can I use async data in Angular by loading it once?如何通过加载一次来在 Angular 中使用异步数据?
【发布时间】:2019-12-19 23:12:34
【问题描述】:

我有一个 data.service.ts 我想在其中预加载 2 个数组:

public aspects: DTO.Aspect[];
public reports: DTO.Report[];

constructor(dao: DaoService){
   // Getting Reports
   this.dao.getReports().subscribe(reps: DTO.Report[] => {
      this.reports = reps;
   })

   //Getting Aspects of each Report
   for(let i = 0; i < this.reports.length; i++){
      this.dao.getAspects(this.reports[i].reportId).subscribe(asps: DTO.Report[] => {
         this.aspects = asps;
      })
   }
}

仅供参考 dao.service.ts

import { Injectable } from "@angular/core";
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Observable, throwError } from "rxjs";
import { catchError, retry } from "rxjs/operators";
import { DTO } from "./dto";

@Injectable({
  providedIn: "root"
})
export class Dao {
  private static API: String =
    "https://apiapiapiapi.com";
  constructor(private http: HttpClient) {}

getAspects(repId: string): Observable<DTO.Aspect[]> {
    console.log(Dao.API + "aspect/report/" + repId);
    return this.http
      .get<DTO.Aspect[]>(Dao.API + "aspect/report/" + repId)
      .pipe(retry(3), catchError(this.handleErrors));
  }

getReports(): Observable<DTO.Report[]> {
    console.log(Dao.API + "report/current");
    return this.http
      .get<DTO.Report[]>(Dao.API + "report/current")
      .pipe(retry(3), catchError(this.handleErrors));
  }
}

之后我想在我的应用程序的任何地方使用这些数组。例如,当我单击报告的侧边栏时,它应该将我引导到显示该报告所有方面的页面:

report.component.ts

routerLink: localhost:4200/report/512

import { Component, OnInit } from "@angular/core";
import { Router, ActivatedRoute, ParamMap } from "@angular/router";
import { DataService } from "@core/data.service";
import { DTO } from "@core/dto";
import { Dao } from "@core/dao.service";

@Component({
  selector: "app-report",
  template: "<div *ngFor='let aspect of aspects'>
                <span>{{aspect.aspectName}}</span>
             </div>",
  styleUrls: ["./report.component.scss"]
})
export class ReportComponent implements OnInit {
aspects: DTO.Aspect[] = [];

constructor(data: DataService, route: ActivatedRoute){
   // Get the Report ID from the URL
   this.route.paramMap.subscribe(params => {
      const reportId = params.get("reportId");

      // Use the Report ID (512) as parameter to find the Aspects of this Report
      this.aspects = this.data.aspects.filter(aspect => {   // aspects in data.service.ts are not loaded yet
         return aspect.reportId == reportId;
      })
   })
}
}

问题是 data.service.ts 中的方面还没有加载。我通过创建自己的观察者或使用 Promises 尝试了一些事情,但性能变得更糟。所以我的问题是:

我怎样才能加载数据一次并在它们准备好时使用它们?

【问题讨论】:

  • 请粘贴您的 dao 服务
  • 你的订阅调用是假的,应该是 this.dao.getReports().subscribe(reps => this.reports = reps);这段代码还能编译吗?
  • @mwe 你是对的。我将此代码作为当前问题的示例输入到我的 stackoverflow 文章中。我刚刚编辑了它。
  • 您可以将数据缓存在 localStor 或 sessionStore 中。
  • @PedroB。我不知道在这种情况下你需要什么 dao 服务,但我在文章中添加了它。只有 2 个方法返回 observables。当给定reportId作为参数时,一个带有报告,一个带有方面

标签: angular typescript


【解决方案1】:

首先我建议你深入研究 rxjs,因为它有很多功能可以处理这类东西。

您的代码的问题是 for 可能在 getReports 完成之前执行,因此 for 将无法访问这些项目。

这可以通过多种方式解决,这个 sn-p 将允许您只等待 1 个 observable 而不是每个报告一个和每个方面一个。

public aspects: DTO.Aspect[];
public reports: DTO.Report[];

constructor(dao: DaoService) { }

public getAllAspects() {
    return this.dao.getReports().pipe(
        // switchMap, this is to pass in the result of the first observable into the second one 
        switchMap(reports =>
            this.getAspectsForReports(reports)
        )
    ),
}

public getAspectsForReports(reports) {
    let aspectsObservables = [];
    for (let i = 0; i < reports.length; i++) {
        aspectsObservables.push(this.dao.getAspects(reports[i].reportId));
    }

    return forkJoin(aspectsObservables); // Accepts an Array of ObservableInput or a dictionary Object of ObservableInput and returns an Observable that emits either an array of values in the exact same order as the passed array, or a dictionary of values in the same shape as the passed dictionary.  
}

然后我会在导航到报告页面/组件之前执行此操作,然后当页面加载时,数据将已经加载到 DataService 中

this.data.getAllAspects() // Will return an array of aspects
    .subscribe(aspects => {
        this.data.aspects = aspects;
        this.navigate(['/report/:id']);
    })

这种方法的问题是getAspectsForReports 的逻辑将在您订阅时被调用...

有用的链接:

【讨论】:

  • 这种方式的问题是每次订阅this.data.getAllAspects()时都会执行getAspectsForReports()的逻辑
  • 虽然他问“我怎样才能加载一次数据并在它们准备好时使用它们”......所以你可以在加载应用程序时订阅一次。不需要多次订阅,如果他将数组保存在Service中,他可以引用属性data.aspects
【解决方案2】:

我建议公开 Observables 本身而不是它们的值。 所以你的服务看起来像:

    @Injectable({provideIn: 'root'})
    export class DataService {

        // With a ReplySubject no matter when you subscribe you'll get the last emitted value.
        private reports$: ReplaySubject<Array<DTO.Report>> = new ReplaySubject(1);
        private aspects$: ReplaySubject<Array<DTO.Aspects>> = new ReplaySubject(1);

        constructor(dao: DaoService){
           // Getting Reports
            this.dao.getReports().subscribe(this.reports$);

           this.reports$
                 .pipe(
                   switchMap(reports: Array<DTO.Report>)=> { 
                   // Getting Aspects of first Report only (for simplicity)             
                   return this.dao.getAspects(reports[0]);
                   })
                 .subscribe(this.aspects$);
        }

        getReports(): Observable<Array<DTO.Report>>{    
        return this.reports$.asObservable();
        }

        getAspects(): Observable<Array<DTO.Aspect>>{
        return this.aspects$.asObservable();
        }

   }

那么在组件中你必须订阅你的data.service.tsgetAspects()

在这里,我还建议利用async 管道的订阅,而不是手动处理订阅,但这只是我的感激之情。

编辑 1: 如果您想知道如何处理原始问题中的 for 循环,请查看此处发布的答案: Loop array and return data for each id in Observable

【讨论】:

  • 不错的解决方案,虽然对于 Angular 和 RxJs 的新手来说有点复杂。我想我自己会去的!
猜你喜欢
  • 2019-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多