storage 是 firebase 所提供的儲存服務,可以想像是 firebase 版本的雲端硬碟,只不過還加上許多實用的功能。
免費有提供一定的容量和流量,可以放心使用與放心玩
首先在 app.module.ts
引入 AngularFireStorageModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularFireModule } from '@angular/fire/compat';
// 引入
import { AngularFireStorageModule } from '@angular/fire/compat/storage';
import { environment } from '../environments/environment';
@NgModule({
imports: [
BrowserModule,
AngularFireModule.initializeApp(environment.firebase),
// 引入
AngularFireStorageModule
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {}
再來設定你的 BUCKET
providers: [
{ provide: BUCKET, useValue: environment.firebase.storageBucket },
],
這樣就設定完成囉
再來回到 checkinService
上傳圖片的功能實作部分
uploadFile(
imageFiles: File[],
filePath: string,
docPath: string,
message: string,
name: string
): Observable<any> {
const nowTimestamp = +new Date();
const fullFilePath = `checkin/${filePath}`;
let fileArray$ = [];
for (const [i, imageFile] of Object.entries(imageFiles)) {
const task = this.storage.upload(
`${fullFilePath}${nowTimestamp}${i}`,
imageFile
);
fileArray$.push(task);
}
return forkJoin(fileArray$).pipe(
finalize(() => {
fileArray$.forEach((e, i) => {
const fileRef = this.storage.ref(
`${fullFilePath}${nowTimestamp}${i}`
);
const downloadURL$ = fileRef.getDownloadURL();
downloadURL$.subscribe((imageUrl) => {
if (Number(i) === 0) {
this.sendMessageToLineChatbot(message, name, imageUrl, filePath);
}
this.firestore.doc(docPath).update({
imgFile: firebase.firestore.FieldValue.arrayUnion(imageUrl),
docPath: filePath,
});
});
});
})
);
}
總共會使用到5個參數
imageFiles: File[]
: 上傳的檔案filePath: string,
:上傳的檔案放在firebase storage的路徑,路徑已存在firestore 建立的id 為主docPath: string,
: firestore上傳的路徑位址,圖片上傳成功後,要將圖片的URL 更新回 firestore 的資料中,記錄下來message: string,
: 圖片上傳成功後,要將打卡人的打卡內容作為訊息傳送到 line message apiname: string
: 圖片上傳成功後,要將打卡人的名字做為訊息傳送到 line message apiconst fullFilePath = `checkin/${filePath}`;
const nowTimestamp = +new Date();
let fileArray$ = [];
for (const [i, imageFile] of Object.entries(imageFiles)) {
const task = this.storage.upload(
`${fullFilePath}${nowTimestamp}${i}`,
imageFile
);
fileArray$.push(task);
}
一開始先定義三個變數
準備好之後,就開始執行上傳的工作
return forkJoin(fileArray$).pipe(
finalize(() => {
fileArray$.forEach((e, i) => {
const fileRef = this.storage.ref(`${fullFilePath}${nowTimestamp}${i}`);
const downloadURL$ = fileRef.getDownloadURL();
downloadURL$.subscribe((imageUrl) => {
if (Number(i) === 0) {
this.sendMessageToLineChatbot(message, name, imageUrl, filePath);
}
this.firestore.doc(docPath).update({
imgFile: firebase.firestore.FieldValue.arrayUnion(imageUrl),
docPath: filePath,
});
});
});
})
);
最最最最精華的地方就是使用rxjs的 forkJoin
,解決所有非同步的困擾。forkJoin 代表要陣列裡面所有的可被觀察對象都執行成功後,才會繼續執行下一個動作,也就是所有的圖片都上傳成功之後,才會執行下一動。也就不必煩惱哪個會先上傳完,要做甚麼處理等等。
再來使用 finalize
的運算子,顧名思義,也就是前面都完成之後,最後要做什麼。
上傳完畢之後透過 fileRef
取得上傳位址,再呼叫 getDownloadURL
取得圖片的URL
取得之後,再呼叫 sendMessageToLineChatbot
將打卡訊息、名稱、圖片位置、路徑等參數送出去,傳送到 line message api
最後呼叫firestore 更新的功能,將圖片的資訊更新上去,其中一個比較特殊的功能是 firebase.firestore.FieldValue.arrayUnion
,這個意思是說,檢查陣列裡面的內容,如果重複就不動,沒有的就插入,約等於javascript 的 push 的加強版
好了,這樣上傳圖片的功能就完成了
最後檢討一下,這樣的寫法是否有問題。
想信眼尖的人都會感到不對勁,為什麼上傳的圖片裡面還會包含傳送line message api的訊息呢?這樣讓功能名稱誤導人又違反單一職責原理。
沒錯,這樣的寫法的確不好,有改善的空間,要快快樂樂地寫 side project,但不是製造麻煩給未來的自己,這一段要改進!