1. 課程內容
在本程式碼研究室中,您可以使用 AngularFire 最新的 Angular 程式庫,利用即時協作地圖打造旅遊網誌。最終版網頁應用程式包含旅遊網誌,您可以將圖片上傳到每個曾經造訪的地點。
AngularFire 會用於建構網頁應用程式、用於本機測試的模擬器套件、驗證來追蹤使用者資料、透過 Firestore 和 Storage 保存資料和媒體,並採用 Cloud Functions 技術;最後則是使用 Firebase 託管部署應用程式。
課程內容
- 如何使用模擬器套件在本機使用 Firebase 產品進行開發
- 如何利用 AngularFire 強化網頁應用程式
- 如何將資料保存在 Firestore 中
- 如何將媒體保存在儲存空間中
- 如何將應用程式部署至 Firebase 託管
- 如何使用 Cloud Functions 與資料庫和 API 互動
事前準備
- Node.js 10 或以上版本
- 用來建立及管理 Firebase 專案的 Google 帳戶
- Firebase CLI 11.14.2 以上版本
- 您選擇的瀏覽器,例如 Chrome
- 對 Angular 和 JavaScript 有基本瞭解
2. 取得程式碼範例
從指令列複製程式碼研究室的 GitHub 存放區:
git clone https://github.com/firebase/codelab-friendlychat-web
如果尚未安裝 Git,可以將存放區下載為 ZIP 檔案。
GitHub 存放區包含適用於多個平台的範例專案。
本程式碼研究室僅使用網頁架構存放區:
- 📁? 網路架構:在本程式碼研究室中,您將建構的起始程式碼。
安裝依附元件
複製完成後,請先在根資料夾和 functions
資料夾中安裝依附元件,再建構網頁應用程式。
cd webframework && npm install
cd functions && npm install
安裝 Firebase CLI
在終端機中使用下列指令安裝 Firebase CLI:
npm install -g firebase-tools
請使用以下指令檢查 Firebase CLI 版本是否大於 11.14.2:
firebase --version
如果您的版本低於 11.14.2,請使用以下版本更新:
npm update firebase-tools
3. 建立及設定 Firebase 專案
建立 Firebase 專案
- 登入 Firebase。
- 在 Firebase 控制台中,按一下「新增專案」,然後將 Firebase 專案命名為 <your-project>。記下 Firebase 專案的專案 ID。
- 按一下 [Create Project] (建立專案)。
重要事項:您的 Firebase 專案會命名為 <your-project>,但 Firebase 會自動為其指派專屬專案 ID,格式為 <your-project>-1234。這是專案實際識別方式 (包括在 CLI 中) 的識別方式,<your-project> 則只是顯示名稱。
我們要建構的應用程式使用了適用於網頁應用程式的 Firebase 產品:
- Firebase 驗證:方便使用者登入您的應用程式。
- Cloud Firestore,可將結構化資料儲存至雲端,並在資料變更時立即發送通知。
- Cloud Storage for Firebase,將檔案儲存在雲端。
- Firebase 代管:用於代管及提供資產。
- 與內部和外部 API 互動的函式。
部分產品需要特殊設定,或透過 Firebase 控制台啟用。
將 Firebase 網頁應用程式新增至專案
- 按一下網站圖示,建立新的 Firebase 網頁應用程式。
- 在下一個步驟中,您會看到設定物件。將這個物件的內容複製到
environments/environment.ts
檔案。
啟用 Google 登入 Firebase 驗證
為允許使用者透過 Google 帳戶登入網頁應用程式,我們將使用 Google 登入方式。
如何啟用 Google 登入功能:
- 在 Firebase 控制台中,找到左側面板中的「Build」區段。
- 依序點選「驗證」和「登入方式」分頁標籤 (或按這裡直接前往)。
- 啟用 Google 登入服務供應商,然後按一下「儲存」。
- 將應用程式的公開名稱設為 <your-project-name>,然後從下拉式選單中選擇專案支援電子郵件。
啟用 Cloud Firestore
- 在 Firebase 控制台的「建構」專區中,按一下「Firestore 資料庫」。
- 按一下 Cloud Firestore 窗格中的「建立資料庫」。
- 設定 Cloud Firestore 資料的儲存位置。你可以保留預設值,或選擇附近的區域。
啟用 Cloud Storage
網頁應用程式會使用 Cloud Storage for Firebase 儲存、上傳及分享相片。
- 在 Firebase 控制台的「建構」專區中,按一下「儲存空間」。
- 如果沒看到「開始使用」按鈕,表示 Cloud Storage 已在
,您不需要執行下列步驟。
- 點選「Get Started」(開始使用)。
- 詳閱 Firebase 專案安全性規則免責事項,然後點選「下一步」。
- 系統已根據您為 Cloud Firestore 資料庫選擇的區域,預先選取 Cloud Storage 位置。按一下「完成」即可完成設定。
套用預設安全性規則之後,任何經過驗證的使用者都能將任何內容寫入 Cloud Storage。本程式碼研究室稍後會進一步提升儲存空間的安全性。
4. 連結至 Firebase 專案
透過 Firebase 指令列介面 (CLI),你可以使用 Firebase 託管功能在本機提供網頁應用程式,並將網頁應用程式部署至 Firebase 專案。
確認指令列正在存取應用程式的本機 webframework
目錄。
將網頁應用程式的程式碼連結至 Firebase 專案。首先,在指令列中登入 Firebase CLI:
firebase login
接著執行下列指令,建立專案別名。將 $YOUR_PROJECT_ID
替換為 Firebase 專案的 ID。
firebase use $YOUR_PROJECT_ID
新增 AngularFire
如要將 AngularFire 加入應用程式,請執行下列指令:
ng add @angular/fire
接著,按照指令列指示操作,選取 Firebase 專案中的功能。
初始化 Firebase
如要初始化 Firebase 專案,請執行:
firebase init
接著,在指令列提示中,選取已在 Firebase 專案中使用的功能和模擬器。
啟動模擬器
在 webframework
目錄中執行下列指令,啟動模擬器:
firebase emulators:start
最終,您應該會看到如下內容:
$ firebase emulators:start
i emulators: Starting emulators: auth, functions, firestore, hosting, functions
i firestore: Firestore Emulator logging to firestore-debug.log
i hosting: Serving hosting files from: public
✔ hosting: Local server: http://localhost:5000
i ui: Emulator UI logging to ui-debug.log
i functions: Watching "/functions" for Cloud Functions...
✔ functions[updateMap]: firestore function initialized.
┌─────────────────────────────────────────────────────────────┐
│ ✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://localhost:4000 │
└─────────────────────────────────────────────────────────────┘
┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator │ Host:Port │ View in Emulator UI │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Functions │ localhost:5001 │ http://localhost:4000/functions │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8080 │ http://localhost:4000/firestore │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Hosting │ localhost:5000 │ n/a │
└────────────────┴────────────────┴─────────────────────────────────┘
Emulator Hub running at localhost:4400
Other reserved ports: 4500
Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
看到 ✔All emulators ready!
訊息後,表示模擬器已可供使用。
您應該會看到旅遊應用程式的 UI,但目前無法正常運作:
現在就來建構吧!
5. 將網頁應用程式連結至模擬器
根據模擬器記錄檔中的表格,Cloud Firestore 模擬器正在監聽通訊埠 8080,且驗證模擬器正在監聽通訊埠 9099。
開啟 EmulatorUI
透過網路瀏覽器前往 http://127.0.0.1:4000/。您應該會看到 Emulator 套件 UI。
轉送應用程式以使用模擬器
在 src/app/app.module.ts
中,將下列程式碼新增至 AppModule
的匯入項目清單:
@NgModule({
declarations: [...],
imports: [
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideAuth(() => {
const auth = getAuth();
if (location.hostname === 'localhost') {
connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true });
}
return auth;
}),
provideFirestore(() => {
const firestore = getFirestore();
if (location.hostname === 'localhost') {
connectFirestoreEmulator(firestore, '127.0.0.1', 8080);
}
return firestore;
}),
provideFunctions(() => {
const functions = getFunctions();
if (location.hostname === 'localhost') {
connectFunctionsEmulator(functions, '127.0.0.1', 5001);
}
return functions;
}),
provideStorage(() => {
const storage = getStorage();
if (location.hostname === 'localhost') {
connectStorageEmulator(storage, '127.0.0.1', 5001);
}
return storage;
}),
...
]
應用程式現已設為使用本機模擬器,可讓您在本機進行測試與開發。
6. 新增驗證方式
應用程式設定好模擬器後,就可以加入驗證功能,確保每位使用者在張貼訊息前都已經登入。
如要這麼做,我們可以直接從 AngularFire 匯入 signin
函式,並使用 authState
函式追蹤使用者的驗證狀態。修改登入頁面的函式,讓頁面在載入時檢查使用者驗證狀態。
插入 AngularFire 驗證
在 src/app/pages/login-page/login-page.component.ts
中,從 @angular/fire/auth
匯入 Auth
,並將其插入 LoginPageComponent
。驗證服務供應商 (例如 Google) 和函式 (例如 signin
、signout
) 也可以使用相同套件直接匯入,並用於應用程式中。
import { Auth, GoogleAuthProvider, signInWithPopup, signOut, user } from '@angular/fire/auth';
export class LoginPageComponent implements OnInit {
private auth: Auth = inject(Auth);
private provider = new GoogleAuthProvider();
user$ = user(this.auth);
constructor() {}
ngOnInit(): void {}
login() {
signInWithPopup(this.auth, this.provider).then((result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
return credential;
})
}
logout() {
signOut(this.auth).then(() => {
console.log('signed out');}).catch((error) => {
console.log('sign out error: ' + error);
})
}
}
登入頁面現在開始運作了!嘗試登入,並在 Authentication Emulator 中查看結果。
7. 設定 Firestore
在這個步驟中,您將新增功能來發布和更新儲存在 Firestore 中的旅遊網誌文章。
與驗證功能類似,已預先封裝 AngularFire 中的 Firestore 函式。每份文件都屬於一個集合,而每份文件也可以包含巢狀集合。你必須瞭解 Firestore 中的文件 path
,才能建立及更新旅遊網誌文章。
導入 TravelService
由於許多不同頁面都需要在網頁應用程式中讀取及更新 Firestore 文件,因此我們可以在 src/app/services/travel.service.ts
中實作函式,避免在每個頁面重複插入相同的 AngularFire 函式。
請先插入 Auth
(與上一個步驟類似),並將 Firestore
插入服務。定義可觀測的 user$
物件來監聽目前的驗證狀態,這也相當實用。
import { doc, docData, DocumentReference, Firestore, getDoc, setDoc, updateDoc, collection, addDoc, deleteDoc, collectionData, Timestamp } from "@angular/fire/firestore";
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
user$ = authState(this.auth).pipe(filter(user => user !== null), map(user => user!));
router: Router = inject(Router);
新增旅遊貼文
旅遊貼文會以儲存在 Firestore 中的文件的形式存在。由於文件必須包含在集合中,因此包含所有旅遊貼文的集合將命名為 travels
。因此,所有旅遊貼文的路徑都會是 travels/
使用 AngularFire 的 addDoc
函式,即可將物件插入集合:
async addEmptyTravel(userId: String) {
...
addDoc(collection(this.firestore, 'travels'), travelData).then((travelRef) => {
collection(this.firestore, `travels/${travelRef.id}/stops`);
setDoc(travelRef, {... travelData, id: travelRef.id})
this.router.navigate(['edit', `${travelRef.id}`]);
return travelRef;
})
}
更新及刪除資料
有鑑於任何旅遊貼文的 uid,任一者可以推斷儲存在 Firestore 中文件的路徑,然後使用 AngularFire 的 updateFoc
和 deleteDoc
函式讀取、更新或刪除:
async updateData(path: string, data: Partial<Travel | Stop>) {
await updateDoc(doc(this.firestore, path), data)
}
async deleteData(path: string) {
const ref = doc(this.firestore, path);
await deleteDoc(ref)
}
以可觀察的方式讀取資料
由於旅遊貼文和停靠站在建立後可以修改,因此將文件物件視為可觀測,進而訂閱任何變更,會更加實用。這項功能是由 @angular/fire/firestore
的 docData
和 collectionData
函式提供。
getDocData(path: string) {
return docData(doc(this.firestore, path), {idField: 'id'}) as Observable<Travel | Stop>
}
getCollectionData(path: string) {
return collectionData(collection(this.firestore, path), {idField: 'id'}) as Observable<Travel[] | Stop>span>[]
}
在旅遊貼文中新增停靠點
旅遊貼文功能設定完成後,就可以考慮停靠站。停靠站位於旅遊貼文的子集合中,如下所示:travels/
這幾乎和製作旅遊貼文的情況幾乎相同,不妨試著自行實作,或參閱下列實作方法:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
太好了!旅遊服務中已實作 Firestore 函式,您現在可以查看實際運作情況。
在應用程式中使用 Firestore 函式
前往 src/app/pages/my-travels/my-travels.component.ts
並插入 TravelService
,即可使用其函式。
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
this.travelsData$ = this.travelService.getCollectionData(`travels`) as Observable<Travel[]>
}
系統會在建構函式中呼叫 TravelService
,以取得所有行程的可觀測陣列。
如果您只需要傳輸目前使用者的行程,請使用 query
函式。
確保安全性的其他方法包括實作安全性規則,或搭配使用 Cloud Functions 與 Firestore,詳情請參閱下方選用步驟。
接著,只要呼叫在 TravelService
中實作的函式即可。
async createTravel(userId: String) {
this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
this.travelService.deleteData(`travels/${travelId}`)
}
現在,「我的旅遊」頁面應該就能正常運作了!建立新的旅遊貼文時,Firestore 模擬器會有什麼影響。
接著,針對 /src/app/pages/edit-travels/edit-travels.component.ts
中的更新函式重複上述步驟:
travelService: TravelService = inject(TravelService)
travelId = this.activatedRoute.snapshot.paramMap.get('travelId');
travelData$: Observable<Travel>;
stopsData$: Observable<Stop[]>;
constructor() {
this.travelData$ = this.travelService.getDocData(`travels/${this.travelId}`) as Observable<Travel>
this.stopsData$ = this.travelService.getCollectionData(`travels/${this.travelId}/stops`) as Observable<Stop[]>
}
updateCurrentTravel(travel: Partial<Travel>) {
this.travelService.updateData(`travels${this.travelId}`, travel)
}
updateCurrentStop(stop: Partial<Stop>) {
stop.type = stop.type?.toString();
this.travelService.updateData(`travels${this.travelId}/stops/${stop.id}`, stop)
}
addStop() {
if (!this.travelId) return;
this.travelService.addStop(this.travelId);
}
deleteStop(stopId: string) {
if (!this.travelId || !stopId) {
return;
}
this.travelService.deleteData(`travels${this.travelId}/stops/${stopId}`)
this.stopsData$ = this.travelService.getCollectionData(`travels${this.travelId}/stops`) as Observable<Stop[]>
}
8. 設定儲存空間
您現在可以實作 Storage 來儲存圖片和其他類型的媒體。
Cloud Firestore 最適合用來儲存結構化資料,例如 JSON 物件。Cloud Storage 專為儲存檔案或 blob 而設計,在這個應用程式中,您將使用此功能讓使用者分享自己的旅遊相片。
同樣地,使用 Firestore 儲存及更新檔案時,每個檔案都需要有一個不重複的 ID。
讓我們在 TraveService
中實作函式:
上傳檔案
前往 src/app/services/travel.service.ts
,從 AngularFire 插入 Storage:
export class TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);
並實作上傳函式:
async uploadToStorage(path: string, input: HTMLInputElement, contentType: any) {
if (!input.files) return null
const files: FileList = input.files;
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
if (file) {
const imagePath = `${path}/${file.name}`
const storageRef = ref(this.storage, imagePath);
await uploadBytesResumable(storageRef, file, contentType);
return await getDownloadURL(storageRef);
}
}
return null;
}
從 Firestore 存取文件及從 Cloud Storage 存取檔案的主要差異在於,雖然兩者都會遵循資料夾結構化路徑,但基本網址和路徑的組合是透過 getDownloadURL
取得,然後可以儲存並用於
檔案中。
在應用程式中使用函式
前往 src/app/components/edit-stop/edit-stop.component.ts
並使用以下指令呼叫上傳函式:
async uploadFile(file: HTMLInputElement, stop: Partial<Stop>) {
const path = `/travels/${this.travelId}/stops/${stop.id}`
const url = await this.travelService.uploadToStorage(path, file, {contentType: 'image/png'});
stop.image = url ? url : '';
this.travelService.updateData(path, stop);
}
上傳圖片後,媒體檔案本身會上傳至儲存空間,網址也會據此儲存至 Firestore 中的文件。
9. 部署應用程式
現在可以部署應用程式了!
將 firebase
設定從 src/environments/environment.ts
複製到 src/environments/environment.prod.ts
,然後執行:
firebase deploy
畫面應如下所示:
✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.
=== Deploying to 'friendly-travels-b6a4b'...
i deploying storage, firestore, hosting
i firebase.storage: checking storage.rules for compilation errors...
✔ firebase.storage: rules file storage.rules compiled successfully
i firestore: reading indexes from firestore.indexes.json...
i cloud.firestore: checking firestore.rules for compilation errors...
✔ cloud.firestore: rules file firestore.rules compiled successfully
i storage: latest version of storage.rules already up to date, skipping upload...
i firestore: deploying indexes...
i firestore: latest version of firestore.rules already up to date, skipping upload...
✔ firestore: deployed indexes in firestore.indexes.json successfully for (default) database
i hosting[friendly-travels-b6a4b]: beginning deploy...
i hosting[friendly-travels-b6a4b]: found 6 files in .firebase/friendly-travels-b6a4b/hosting
✔ hosting[friendly-travels-b6a4b]: file upload complete
✔ storage: released rules storage.rules to firebase.storage
✔ firestore: released rules firestore.rules to cloud.firestore
i hosting[friendly-travels-b6a4b]: finalizing version...
✔ hosting[friendly-travels-b6a4b]: version finalized
i hosting[friendly-travels-b6a4b]: releasing new version...
✔ hosting[friendly-travels-b6a4b]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/friendly-travels-b6a4b/overview
Hosting URL: https://friendly-travels-b6a4b.web.app
10. 恭喜!
現在應用程式應已完成並部署至 Firebase 託管!您現在可以在 Firebase 控制台中存取所有資料與數據分析。
如需更多有關 AngularFire、函式、安全性規則的功能,別忘了參考下方選用步驟,以及其他 Firebase 程式碼研究室!
11. 選用:AngularFire 驗證防護機制
除了 Firebase 驗證功能之外,AngularFire 也會提供路線驗證防護機制,讓存取權不足的使用者能夠重新導向。這有助於防止應用程式存取受保護的資料。
在 src/app/app-routing.module.ts
中匯入
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from '@angular/fire/auth-guard'
接著,您可以將函式定義為在特定網頁,以及重新導向的使用者位置和位置:
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['signin']);
const redirectLoggedInToTravels = () => redirectLoggedInTo(['my-travels']);
然後將它們新增至您的路徑即可:
const routes: Routes = [
{path: '', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'signin', component: LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectLoggedInToTravels}},
{path: 'my-travels', component: MyTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
{path: 'edit/:travelId', component: EditTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe: redirectUnauthorizedToLogin}},
];
12. 選用:安全性規則
Firestore 和 Cloud Storage 都使用安全性規則 (分別為 firestore.rules
和 security.rules
) 來強制執行安全防護機制並驗證資料。
目前 Firestore 和 Storage 資料開放讀取和寫入,但您不想讓員工變更其他資料。」貼文!您可以使用安全性規則來限制集合和文件的存取權。
Firestore 規則
如果只要允許通過驗證的使用者查看旅遊貼文,請前往 firestore.rules
檔案新增:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/travels {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
安全性規則也可用於驗證資料:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/posts {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
&& "author" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
儲存空間規則
同樣地,我們可以利用安全性規則,強制對 storage.rules
中的儲存空間資料庫進行存取。請注意,我們也可以使用函式進行更複雜的檢查:
rules_version = '2';
function isImageBelowMaxSize(maxSizeMB) {
return request.resource.size < maxSizeMB * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
service firebase.storage {
match /b/{bucket}/o {
match /{userId}/{postId}/{filename} {
allow write: if request.auth != null
&& request.auth.uid == userId && isImageBelowMaxSize(5);
allow read;
}
}
}