如何在 Angular 中定义订阅 Observables 的执行顺序?

How can I define the execution order of subscription to Observables in Angular?

我从 service.ts 文件中的 API 收到了一些 get-Call:

//code
getCars()
{
this.car = this.http.get(car_url)
return this.car;
}

getTires()
{
this.tires = this.http.get(tires_url)
return this.tires;
}

getSeats()
{
this.seats = this.http.get(seats_url)
return this.seats;
}

detail.compoenent.ts中,我将这些数据过滤到选定的汽车,然后通过detail.component.html呈现- detail.compoenent.ts 看起来像这样:

//code
ngOnInit() 
{
this.Service.getCars()
.subscribe(cars => this.car = cars.find(/*code (selecting car with certain car_id*/)

this.Service.getTires()
.subscribe(tires => {this.tires = tires.filter(/*code (selecting all the tires with the selected car_id*/)}

this.Service.getSeats()
.subscribe(seats => {this.seats = seats.filter(/*code (selecting all the seats with the selected car_id*/)}
}

要过滤轮胎和座椅,必须先执行getCar(),因为需要它的信息来过滤轮胎和座椅。那么我该如何放置代码以确保 this.Service.getCars()subscribe(/code/) 在 this.Service.getTires().subscribe(/code/) 和 this.Service.getSeats().subscribe(/code/)?

你可以使用mergeMap()然后调用其他的observables,你可以在另一个API中使用另一个之前的数据。

this.Service.getCars().pipe(mergeMap((cars) => {
    // cars is available for the other two
    return this.Service.getTires().pipe(mergeMap((tierData) => {
        // tierData is avaibale for getting seats.
        return this.Service.getSeats()
    }))
})).subscribe((seats) => {

})

您可以将其他两个合并到第一个管道中。

简单的解决方案可以是在回调函数中进行后续的 http 请求。

ngOnInit() 
{
    this.Service.getCars().subscribe(
      cars => {
           this.car = cars.find();
           this.Service.getTires().subscribe(tires => {
              this.tires = tires.filter();
              this.Service.getSeats().subscribe(
                seats => {this.seats = 
              seats.filter(/*code (selecting all 
                 the seats with the selected car_id*/)}
            }
         }

      });

}

在加载所有数据之前,您可以向最终用户显示微调器。

嵌套订阅是一种很大的代码味道。如果您觉得需要嵌套订阅,请搜索更高阶的可观察对象,如 switchMap、concatMap、mergeMap、zip 等。

我花时间构建了您要实现的目标的完整演示。

首先,让我们开始定义我们的接口以具有某种类型安全性:

export interface Car {
  id: string;
  seatId: string;
  tyreId: string;
}

export type CarResolved = Omit<Car, "seatId" | "tyreId"> & {
  seat: Seat;
  tyre: Tyre;
};

export interface Tyre {
  id: string;
  diameter: number;
}

export interface Seat {
  id: string;
  width: number;
}

现在我们知道我们想要什么样的数据结构,让我们构建一个服务和 return 模拟数据,您稍后可以用您的后端替换这些数据:

@Injectable()
export class ResourcesService {
  public getCars(): Observable<CarResolved[]> {
    return this._getCars().pipe(
      switchMap(cars =>
        forkJoin(
          ...cars.map(car =>
            forkJoin(
              this._getTyreById(car.tyreId),
              this._getSeatById(car.seatId)
            ).pipe(
              map(([tyre, seat]) => ({
                id: car.id,
                seat,
                tyre
              }))
            )
          )
        )
      )
    );
  }

  private _getCars(): Observable<Car[]> {
    return of(mockCars);
  }

  private _getTyreById(id: string): Observable<Tyre> {
    return of(mockTyres.find(tyre => tyre.id === id));
  }

  private _getSeatById(id: string): Observable<Seat> {
    return of(mockSeats.find(seat => seat.id === id));
  }
}

const mockCars: Car[] = [
  { id: "car-1", seatId: "seat-1", tyreId: "tyre-1" },
  { id: "car-2", seatId: "seat-2", tyreId: "tyre-2" },
  { id: "car-3", seatId: "seat-1", tyreId: "tyre-3" }
];

const mockTyres: Tyre[] = [
  { id: "tyre-1", diameter: 80 },
  { id: "tyre-2", diameter: 60 },
  { id: "tyre-3", diameter: 75 }
];

const mockSeats: Seat[] = [
  { id: "seat-1", width: 10 },
  { id: "seat-2", width: 20 },
  { id: "seat-3", width: 30 }
];

如果您查看 getCars 方法,它绝对没有订阅。它只是 return 一个有人可以稍后订阅的可观察对象。一切都通过流完成:)

最后,最简单的部分:视图。

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  public cars$: Observable<CarResolved[]> = this.resourcesService.getCars();

  constructor(private resourcesService: ResourcesService) {}
}

这里也请注意:完全没有订阅! :)

HTML 方:

<pre>{{ cars$ | async | json }}</pre>

我们的视图现在显示已解决的汽车数组:

[
  {
    "id": "car-1",
    "seat": {
      "id": "seat-1",
      "width": 10
    },
    "tyre": {
      "id": "tyre-1",
      "diameter": 80
    }
  },
  {
    "id": "car-2",
    "seat": {
      "id": "seat-2",
      "width": 20
    },
    "tyre": {
      "id": "tyre-2",
      "diameter": 60
    }
  },
  {
    "id": "car-3",
    "seat": {
      "id": "seat-1",
      "width": 10
    },
    "tyre": {
      "id": "tyre-3",
      "diameter": 75
    }
  }
]

这是 stackblitz 上的现场演示:https://stackblitz.com/edit/angular-qypary