将一个 Observable<string> 和一个 Array<Observable<string>> 组合成一个 Observable<{string, string[]}>
Combine an Observable<string> and an Array<Observable<string>> into a single Observable<{string, string[]}>
我有一个看起来像这样的表格:
Program Name
和 Description
是 Reactive Form 的一部分。 Feature Image
和 Video(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 个问题
您没有使用默认 Observable
值初始化 featureImageUrl$
。当 forkJoin
收到一个非 可观察的 值作为其参数之一(在本例中 undefined
),它将引发异常。
当没有选择视频时,您将一个空列表传递给内部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
中看到它的工作
我有一个看起来像这样的表格:
Program Name
和 Description
是 Reactive Form 的一部分。 Feature Image
和 Video(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 个问题
您没有使用默认
Observable
值初始化featureImageUrl$
。当forkJoin
收到一个非 可观察的 值作为其参数之一(在本例中undefined
),它将引发异常。当没有选择视频时,您将一个空列表传递给内部
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
中看到它的工作