Codelab เกี่ยวกับเฟรมเวิร์กเว็บของ Firebase Angular

1. สิ่งที่คุณจะสร้าง

ในโค้ดแล็บนี้ คุณจะได้สร้างบล็อกท่องเที่ยวพร้อมแผนที่ที่ทำงานร่วมกันแบบเรียลไทม์ด้วยข้อมูลล่าสุดจากไลบรารี Angular ของเรา นั่นคือ AngularFire เว็บแอปสุดท้ายจะประกอบด้วยบล็อกท่องเที่ยวที่คุณสามารถอัปโหลดรูปภาพไปยังแต่ละสถานที่ที่คุณเคยไป

เราจะใช้ AngularFire เพื่อสร้างเว็บแอป, ชุดโปรแกรมจำลองสำหรับการทดสอบในเครื่อง, การตรวจสอบสิทธิ์เพื่อติดตามข้อมูลผู้ใช้, Firestore และพื้นที่เก็บข้อมูลเพื่อจัดเก็บข้อมูลและสื่อ โดยใช้ Cloud Functions และสุดท้ายคือ Firebase Hosting เพื่อติดตั้งใช้งานแอป

สิ่งที่คุณจะได้เรียนรู้

  • วิธีพัฒนาด้วยผลิตภัณฑ์ Firebase ในเครื่องด้วย Emulator Suite
  • วิธีเพิ่มประสิทธิภาพเว็บแอปด้วย AngularFire
  • วิธีจัดเก็บข้อมูลใน Firestore อย่างถาวร
  • วิธีจัดเก็บสื่ออย่างถาวรในพื้นที่เก็บข้อมูล
  • วิธีทําให้แอปใช้งานได้กับโฮสติ้งของ Firebase
  • วิธีใช้ Cloud Functions เพื่อโต้ตอบกับฐานข้อมูลและ API

สิ่งที่ต้องมี

  • Node.js เวอร์ชัน 10 ขึ้นไป
  • บัญชี Google สำหรับการสร้างและการจัดการโปรเจ็กต์ Firebase
  • Firebase CLI เวอร์ชัน 11.14.2 ขึ้นไป
  • เบราว์เซอร์ที่คุณเลือก เช่น Chrome
  • ความเข้าใจพื้นฐานเกี่ยวกับ Angular และ JavaScript

2. รับโค้ดตัวอย่าง

โคลนที่เก็บ GitHub ของ Codelab จากบรรทัดคำสั่งโดยใช้คำสั่งต่อไปนี้

git clone https://github.com/firebase/codelab-friendlychat-web

หรือหากไม่ได้ติดตั้ง git คุณสามารถดาวน์โหลดที่เก็บเป็นไฟล์ ZIP

ที่เก็บ GitHub มีโปรเจ็กต์ตัวอย่างสำหรับหลายแพลตฟอร์ม

Codelab นี้ใช้เฉพาะที่เก็บเว็บเฟรมเวิร์ก

  • 📁 webframework: โค้ดเริ่มต้นที่คุณจะใช้ต่อยอดใน Codelab นี้

ติดตั้งการอ้างอิง

หลังจากโคลนแล้ว ให้ติดตั้งการอ้างอิงในโฟลเดอร์รูทและ 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

  1. ลงชื่อเข้าใช้คอนโซล Firebase โดยใช้บัญชี Google
  2. คลิกปุ่มเพื่อสร้างโปรเจ็กต์ใหม่ แล้วป้อนชื่อโปรเจ็กต์ (เช่น FriendlyChat)
  3. คลิกต่อไป
  4. หากได้รับแจ้ง ให้อ่านและยอมรับข้อกำหนดของ Firebase แล้วคลิกต่อไป
  5. (ไม่บังคับ) เปิดใช้ความช่วยเหลือจาก AI ในคอนโซล Firebase (เรียกว่า "Gemini ใน Firebase")
  6. สำหรับ Codelab นี้ คุณไม่จำเป็นต้องใช้ Google Analytics ดังนั้นให้ปิดตัวเลือก Google Analytics
  7. คลิกสร้างโปรเจ็กต์ รอให้ระบบจัดสรรโปรเจ็กต์ แล้วคลิกดำเนินการต่อ

เพิ่มเว็บแอป Firebase ลงในโปรเจ็กต์

  1. คลิกไอคอนเว็บเพื่อสร้างเว็บแอป Firebase ใหม่
  2. ในขั้นตอนถัดไป คุณจะเห็นออบเจ็กต์การกำหนดค่า คัดลอกเนื้อหาของออบเจ็กต์นี้ลงในไฟล์ environments/environment.ts

ตั้งค่าผลิตภัณฑ์ Firebase

แอปพลิเคชันที่เราจะสร้างใช้ผลิตภัณฑ์ Firebase ที่พร้อมใช้งานสำหรับเว็บแอป ดังนี้

  • การตรวจสอบสิทธิ์ Firebase เพื่อให้ผู้ใช้ลงชื่อเข้าใช้แอปของคุณได้อย่างง่ายดาย
  • Cloud Firestore เพื่อบันทึกข้อมูลที่มีโครงสร้างไว้ในระบบคลาวด์และรับการแจ้งเตือนทันทีเมื่อข้อมูลมีการเปลี่ยนแปลง
  • Cloud Storage for Firebase เพื่อบันทึกไฟล์ในระบบคลาวด์
  • Firebase Hosting เพื่อโฮสต์และแสดงเนื้อหา
  • ฟังก์ชันสำหรับโต้ตอบกับ API ภายในและภายนอก

ผลิตภัณฑ์บางอย่างเหล่านี้ต้องมีการกำหนดค่าพิเศษหรือต้องเปิดใช้โดยใช้คอนโซล Firebase

เปิดใช้การลงชื่อเข้าใช้ด้วย Google สำหรับการตรวจสอบสิทธิ์ Firebase

หากต้องการอนุญาตให้ผู้ใช้ลงชื่อเข้าใช้เว็บแอปด้วยบัญชี Google เราจะใช้วิธีการลงชื่อเข้าใช้ของ Google

วิธีเปิดใช้การลงชื่อเข้าใช้ Google

  1. ในคอนโซล Firebase ให้หาส่วนสร้างในแผงด้านซ้าย
  2. คลิก Authentication แล้วคลิกแท็บวิธีการลงชื่อเข้าใช้ (หรือคลิกที่นี่เพื่อไปที่แท็บดังกล่าวโดยตรง)
  3. เปิดใช้ผู้ให้บริการลงชื่อเข้าใช้ Google แล้วคลิกบันทึก
  4. ตั้งชื่อที่เปิดเผยต่อสาธารณะของแอปเป็น <your-project-name> แล้วเลือกอีเมลสนับสนุนโปรเจ็กต์จากเมนูแบบเลื่อนลง

เปิดใช้ Cloud Firestore

  1. ในส่วนสร้างของคอนโซล Firebase ให้คลิกฐานข้อมูล Firestore
  2. คลิกสร้างฐานข้อมูลในแผง Cloud Firestore
  3. ตั้งค่าตำแหน่งที่จัดเก็บข้อมูล Cloud Firestore คุณจะปล่อยให้เป็นค่าเริ่มต้นหรือเลือกภูมิภาคที่อยู่ใกล้คุณก็ได้

เปิดใช้ Cloud Storage

เว็บแอปใช้ Cloud Storage for Firebase เพื่อจัดเก็บ อัปโหลด และแชร์รูปภาพ

  1. ในส่วนสร้างของคอนโซล Firebase ให้คลิก Storage
  2. หากไม่มีปุ่มเริ่มต้นใช้งาน แสดงว่า Cloud Storage

เปิดใช้ไว้แล้ว และคุณไม่จำเป็นต้องทำตามขั้นตอนด้านล่าง

  1. คลิกเริ่มต้นใช้งาน
  2. อ่านข้อจำกัดความรับผิดเกี่ยวกับกฎการรักษาความปลอดภัยสำหรับโปรเจ็กต์ Firebase แล้วคลิกถัดไป
  3. ระบบจะเลือกตำแหน่ง Cloud Storage ไว้ล่วงหน้าโดยใช้ภูมิภาคเดียวกันกับที่คุณเลือกสำหรับฐานข้อมูล Cloud Firestore คลิกเสร็จสิ้นเพื่อตั้งค่าให้เสร็จสมบูรณ์

กฎความปลอดภัยเริ่มต้นช่วยให้ผู้ใช้ที่ตรวจสอบสิทธิ์แล้วทุกคนเขียนข้อมูลใดก็ได้ไปยัง Cloud Storage เราจะทำให้พื้นที่เก็บข้อมูลปลอดภัยยิ่งขึ้นในโค้ดแล็บนี้ในภายหลัง

4. เชื่อมต่อกับโปรเจ็กต์ Firebase

อินเทอร์เฟซบรรทัดคำสั่ง (CLI) ของ Firebase ช่วยให้คุณใช้ Firebase Hosting เพื่อแสดงเว็บแอปในเครื่อง รวมถึงทำให้เว็บแอปใช้งานได้ในโปรเจ็กต์ Firebase

ตรวจสอบว่าบรรทัดคำสั่งเข้าถึงไดเรกทอรี webframework ในเครื่องของแอป

เชื่อมต่อโค้ดเว็บแอปกับโปรเจ็กต์ Firebase ก่อนอื่น ให้เข้าสู่ระบบ Firebase CLI ในบรรทัดคำสั่งโดยทำดังนี้

firebase login

จากนั้นเรียกใช้คำสั่งต่อไปนี้เพื่อสร้างชื่อแทนของโปรเจ็กต์ แทนที่ $YOUR_PROJECT_ID ด้วยรหัสของโปรเจ็กต์ Firebase

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/ คุณควรเห็น 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. การเพิ่มการตรวจสอบสิทธิ์

ตอนนี้เราได้ตั้งค่าโปรแกรมจำลองสำหรับแอปแล้ว จึงสามารถเพิ่มฟีเจอร์การตรวจสอบสิทธิ์เพื่อให้แน่ใจว่าผู้ใช้แต่ละรายได้ลงชื่อเข้าใช้ก่อนที่จะโพสต์ข้อความ

โดยเราสามารถนำเข้าฟังก์ชัน signin จาก AngularFire ได้โดยตรง และติดตามสถานะการให้สิทธิ์ของผู้ใช้ด้วยฟังก์ชัน authState แก้ไขฟังก์ชันหน้าเข้าสู่ระบบเพื่อให้หน้าเว็บตรวจสอบสถานะการให้สิทธิ์ของผู้ใช้เมื่อโหลด

การแทรก Auth ของ AngularFire

ใน src/app/pages/login-page/login-page.component.ts ให้นำเข้า Auth จาก @angular/fire/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 เช่นเดียวกับการตรวจสอบสิทธิ์ เอกสารแต่ละรายการจะอยู่ในคอลเล็กชัน และเอกสารแต่ละรายการจะมีคอลเล็กชันที่ซ้อนกันได้ด้วย คุณต้องทราบ path ของเอกสารใน Firestore เพื่อสร้างและอัปเดตบล็อกโพสต์เกี่ยวกับการท่องเที่ยว

การใช้ 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/

การใช้ฟังก์ชัน addDoc จาก AngularFire จะช่วยให้แทรกออบเจ็กต์ลงในคอลเล็กชันได้

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 ได้ ซึ่งจากนั้นจะอ่าน อัปเดต หรือลบได้โดยใช้ฟังก์ชัน updateFoc และ deleteDoc ของ AngularFire

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)
}

อ่านข้อมูลเป็น Observable

เนื่องจากโพสต์การเดินทางและจุดแวะพักระหว่างทางสามารถแก้ไขได้หลังจากสร้างแล้ว การรับออบเจ็กต์เอกสารเป็นออบเซอร์เวเบิลเพื่อติดตามการเปลี่ยนแปลงที่เกิดขึ้นจึงมีประโยชน์มากกว่า ฟังก์ชันนี้ให้บริการโดยฟังก์ชัน docData และ collectionData จาก @angular/fire/firestore

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/

ซึ่งแทบจะเหมือนกับการสร้างโพสต์การเดินทางเลย ดังนั้นลองนำไปใช้ด้วยตนเอง หรือดูการติดตั้งใช้งานด้านล่าง

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 เพื่อใช้ฟังก์ชันของ src/app/pages/my-travels/my-travels.component.ts

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 เหมาะที่สุดสำหรับการจัดเก็บ Structured Data เช่น ออบเจ็กต์ JSON Cloud Storage ออกแบบมาเพื่อจัดเก็บไฟล์หรือ Blob ในแอปนี้ คุณจะใช้เพื่ออนุญาตให้ผู้ใช้แชร์รูปภาพการเดินทาง

เช่นเดียวกับ Firestore การจัดเก็บและอัปเดตไฟล์ด้วย Storage ต้องใช้ตัวระบุที่ไม่ซ้ำกันสำหรับแต่ละไฟล์

มาใช้ฟังก์ชันใน TraveService กัน

การอัปโหลดไฟล์

ไปที่ src/app/services/travel.service.ts แล้วแทรก Storage จาก 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 คือแม้ว่าทั้ง 2 อย่างจะใช้เส้นทางที่มีโครงสร้างโฟลเดอร์เหมือนกัน แต่ระบบจะรับ 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 Hosting แล้ว ตอนนี้คุณจะเข้าถึงข้อมูลและข้อมูลวิเคราะห์ทั้งหมดได้ในคอนโซล Firebase

หากต้องการดูฟีเจอร์เพิ่มเติมเกี่ยวกับ AngularFire, ฟังก์ชัน และกฎความปลอดภัย อย่าลืมดูขั้นตอนที่ไม่บังคับด้านล่าง รวมถึง Codelab ของ Firebase อื่นๆ ด้วย

11. ไม่บังคับ: การป้องกันการตรวจสอบสิทธิ์ของ AngularFire

นอกจาก Firebase Authentication แล้ว 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;
		}
	}
}