将一个 Observable<string> 和一个 Array<Observable<string>> 组合成一个 Observable<{string, string[]}>

Combine an Observable<string> and an Array<Observable<string>> into a single Observable<{string, string[]}>

我有一个看起来像这样的表格:

Program NameDescription 是 Reactive Form 的一部分。 Feature ImageVideo(s) 是文件输入。

我有两个提交表单的按钮,另存为草稿和发布。如果填写了所有字段(包括文件输入),将启用发布。保存为草稿始终处于启用状态并将提交表单。

在表单提交中: 我检查是否有特征图像。如果有,我将其上传到 Firebase 存储桶。我对视频应用相同的检查。如果有 is/are 个视频,我会将它们一个一个地上传到 Firebase 存储桶。这是相同的代码:

let featureImageUrl$: Observable<string>;
const videoUrls$: Observable<string>[] = [];
// Check if featureImageToUpload was uploaded
if (this.featureImageToUpload) {
  featureImageUrl$ = this.fileService.upload(`content/programs/images/${this.utils.generateRandomString()}`, this.featureImageToUpload);
}

// Check if video(s) was/were uploaded
if (this.videosToUpload) {
  this.utils.range(this.videosToUpload.length).forEach(fileIndex => {
    // tslint:disable-next-line:max-line-length
    const videoUrl$ = this.fileService.upload(`content/programs/videos/${this.utils.generateRandomString()}`, this.videosToUpload[fileIndex]);
    videoUrls$.push(videoUrl$);
  });
}

this.fileService.upload 只是一种方法,return 是一个 Observable<string> 本质上包装了已上传文件的下载 URL。

this.utils.range 只是一种方法,return 是一个基于长度的数字数组。所以如果长度是4,那么它将return [0, 1, 2, 3]

问题:

我想创建一个我可以订阅的 Observable,以获得具有如下结构的对象:

{
  featureImageUrl: 'DOWNLOAD URL FOR THE FEATURE IMAGE' || null // null in case the file wasn't uploaded;
  videos: [
    'DOWNLOAD URL FOR VIDEO 1',
    'DOWNLOAD URL FOR VIDEO 2',
    'DOWNLOAD URL FOR VIDEO 3',
    'DOWNLOAD URL FOR VIDEO 4',
    ...
  ] || null // null in case the video(s) were not uploaded;
}

到目前为止我尝试过的事情:

我可以这样使用 forkJoin

forkJoin(...videoUrls$, featureImageUrl$)
  .subscribe(downloadUrls => {
    console.log(downloadUrls);
  });

然后根据videoUrls$的长度提取视频下载网址,然后相应地创建我的对象。但我正在寻找一种更清洁的方法来做到这一点。

我也尝试过使用 Observable.create,但对于这个特定的用例,我无法理解它。

根据Buggy的建议,我也尝试了这个:

forkJoin(forkJoin(videoUrls$), featureImageUrl$)
  .pipe(
    map(([videoUrls, featureImageUrl]) => ({videoUrls, featureImageUrl}))
  );

如果特征图像和视频都被 select 编辑,它工作得很好。但是,如果我只是 select 特色图片而不是 select 任何视频,订阅永远达不到。

这里有一个Sample StackBlitz供您参考。

补充Buggy

建议的方法

目前您的方法存在 2 个问题

  1. 您没有使用默认 Observable 值初始化 featureImageUrl$。当 forkJoin 收到一个非 可观察的 值作为其参数之一(在本例中 undefined),它将引发异常。

  2. 当没有选择视频时,您将一个空列表传递给内部forkJoin。由于 forkJoin 不知道如何处理空的流列表,因此它永远不会发出值。

解决方案如下:

let featureImageUrl$: Observable<string> = of(null);
let videoUrls$: Observable<string[]> = of(null);
if (this.featureImageToUpload) {
  featureImageUrl$ = this.fileToStream(this.featureImageToUpload);
}

if (this.videosToUpload) {
  videoUrls$ = forkJoin(Array.from(this.videosToUpload)
    .map(file => this.fileToStream(file)));
}

forkJoin(videoUrls$, featureImageUrl$)
  .pipe(
    map(([videoUrls, featureImageUrl]) => ({ videoUrls, featureImageUrl }))
  )
  .subscribe(data => {
    console.log({data});
  });

....

private fileToStream(file: File): Observable<string> {
   return this.fileService
     .upload(`content/programs/videos/${this.utils.generateRandomString()}`, file)
}

您可以在 this blitz

中看到它的工作