1. 你將創造什麼
在此 Codelab 中,您將使用我們的 Angular 庫AngularFire中的最新內容建立一個帶有即時協作地圖的旅遊部落格。最終的網路應用程式將包含一個旅遊博客,您可以在其中將圖像上傳到您去過的每個位置。
AngularFire 將用於建立Web 應用程序,模擬器套件用於本地測試,身份驗證用於追蹤用戶數據,Firestore 和儲存用於持久保存數據和媒體,由Cloud Functions 提供支持,最後使用Firebase Hosting 來部署應用程式。
你將學到什麼
- 如何使用 Emulator Suite 在本地開發 Firebase 產品
- 如何使用 AngularFire 增強您的 Web 應用程式
- 如何在 Firestore 中保留數據
- 如何將媒體保存在儲存中
- 如何將您的應用程式部署到 Firebase 託管
- 如何使用 Cloud Functions 與資料庫和 API 交互
你需要什麼
- Node.js版本 10 或更高版本
- 用於建立和管理 Firebase 專案的 Google 帳戶
- Firebase CLI版本 11.14.2 或更高版本
- 您選擇的瀏覽器,例如 Chrome
- 對 Angular 和 Javascript 有基本了解
2. 取得範例程式碼
從命令列克隆 Codelab 的GitHub 儲存庫:
git clone https://github.com/firebase/codelab-friendlychat-web
或者,如果您沒有安裝 git,則可以將儲存庫下載為 ZIP 檔案。
Github 儲存庫包含適用於多個平台的範例專案。
此 Codelab 僅使用 webframework 儲存庫:
- 📁 webframework :您將在本 Codelab 中建構的起始程式碼。
安裝依賴項
克隆後,在建置 Web 應用程式之前在根目錄和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。
- 按一下「建立專案」 。
重要提示:您的 Firebase 專案將命名為<your-project> ,但 Firebase 會自動為其指派一個唯一的專案 ID,格式為<your-project>-1234 。這個唯一識別碼是您的專案的實際識別方式(包括在 CLI 中),而<your-project>只是一個顯示名稱。
我們要建立的應用程式使用可用於網頁應用程式的 Firebase 產品:
- Firebase 驗證可輕鬆讓您的使用者登入您的應用程式。
- Cloud Firestore將結構化資料保存在雲端,並在資料變更時獲得即時通知。
- Cloud Storage for Firebase將檔案保存在雲端。
- Firebase Hosting用於託管和服務您的資產。
- 與內部和外部 API 互動的函數。
其中一些產品需要特殊配置或需要使用 Firebase 控制台啟用。
將 Firebase Web 應用程式加入到專案中
- 按一下 Web 圖示以建立新的 Firebase Web 應用程式。
- 在下一步中,您將看到一個配置物件。將此物件的內容複製到
environments/environment.ts
檔案中。
啟用 Google登入以進行 Firebase 身份驗證
為了允許用戶使用其 Google 帳戶登入網路應用程序,我們將使用Google登入方法。
若要啟用Google登入:
- 在 Firebase 控制台中,找到左側面板中的「建置」部分。
- 按一下「驗證」 ,然後按一下「登入方法」標籤(或按一下此處直接前往此處)。
- 啟用Google登入供應商,然後按一下「儲存」 。
- 將應用程式的面向公眾的名稱設定為<your-project-name>並從下拉式選單中選擇項目支援電子郵件。
啟用 Cloud Firestore
- 在 Firebase 控制台的「建置」部分中,按一下Firestore 資料庫。
- 按一下 Cloud Firestore 窗格中的建立資料庫。
- 設定 Cloud Firestore 資料的儲存位置。您可以將其保留為預設值或選擇離您較近的區域。
啟用雲端儲存
此 Web 應用程式使用 Cloud Storage for Firebase 來儲存、上傳和分享圖片。
- 在 Firebase 控制台的「建置」部分中,按一下「儲存」 。
- 如果沒有「開始」按鈕,則表示雲端儲存已啟用
已啟用,您無需執行以下步驟。
- 單擊開始。
- 閱讀有關 Firebase 專案安全規則的免責聲明,然後按一下「下一步」 。
- 預先選擇的 Cloud Storage 位置與您為 Cloud Firestore 資料庫選擇的區域相同。按一下“完成”完成設定。
使用預設安全規則,任何經過驗證的使用者都可以向 Cloud Storage 寫入任何內容。稍後我們將在此 Codelab 中提高儲存的安全性。
4. 連接到您的 Firebase 項目
Firebase 命令列介面 (CLI) 可讓您使用 Firebase 託管在本地提供您的 Web 應用程序,以及將您的 Web 應用程式部署到 Firebase 專案。
確保您的命令列正在存取應用程式的本機webframework
目錄。
將 Web 應用程式碼連接到您的 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. 將 Web 應用程式連接到模擬器
根據模擬器日誌中的表,Cloud Firestore 模擬器正在偵聽連接埠 8080,並且驗證模擬器正在偵聽連接埠 9099。
開啟模擬器UI
在網頁瀏覽器中,導覽至http://127.0.0.1:4000/ 。您應該會看到模擬器套件 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);
})
}
}
現在登入頁面可以正常使用了!嘗試登錄,並在身份驗證模擬器中查看結果。
7. 配置 Firestore
在此步驟中,您將新增用於發布和更新 Firestore 中儲存的旅遊部落格文章的功能。
與身份驗證類似,Firestore 功能是從 AngularFire 預先打包的。每個文件都屬於一個集合,每個文件也可以有嵌套的集合。建立和更新旅遊部落格文章需要了解 Firestore 中文件的path
。
實施旅遊服務
由於許多不同的頁面需要讀取和更新網頁應用程式中的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[]>
}
在旅行貼文中新增停靠點
現在已經設定了旅行貼文操作,是時候考慮停靠點了,它將存在於旅行貼文的子集合下,如下所示: travels/ /stops/
travels/ /stops/
這與創建旅遊帖子幾乎相同,因此請挑戰自己,自行實現它,或查看下面的實現:
async addStop(travelId: string) {
...
const ref = await addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
setDoc(ref, {...stopData, id: ref.id})
}
好的! Firestore 功能已在 Travel 服務中實現,因此現在您可以看到它們的實際效果。
在應用程式中使用 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
來取得所有旅行的 Observable 陣列。
如果只需要目前使用者的行程,可以使用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. 配置存儲
您現在將實現儲存來儲存圖像和其他類型的媒體。
Cloud Firestore 最適合儲存結構化數據,例如 JSON 物件。雲端儲存旨在儲存檔案或 blob。在此應用程式中,您將使用它來允許用戶分享他們的旅行照片。
與 Firestore 類似,使用 Storage 儲存和更新檔案需要每個檔案有唯一的識別碼。
我們來實作TraveService
中的功能:
上傳文件
導航至src/app/services/travel.service.ts
並從 AngularFire 注入儲存:
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 存取檔案之間的主要區別在於,儘管它們都遵循資料夾結構路徑,但基本 url 和路徑組合是透過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);
}
上傳圖像時,媒體檔案本身將上傳到存儲,並且 url 會相應地儲存在 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 Codelab !
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;
}
}
}