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

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

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

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

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

  • วิธีพัฒนาด้วยผลิตภัณฑ์ Firebase ในเครื่องด้วยชุดโปรแกรมจำลอง
  • วิธีเพิ่มประสิทธิภาพเว็บแอปด้วย 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 ต่อไปนี้

  • 📁 webframework: โค้ดเริ่มต้นที่คุณจะสร้างขึ้นในระหว่าง Codelab นี้

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

หลังจากโคลนแล้ว ให้ติดตั้งทรัพยากร Dependency ในโฟลเดอร์รูทและ 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
  2. ในคอนโซล Firebase ให้คลิกเพิ่มโปรเจ็กต์ แล้วตั้งชื่อโปรเจ็กต์ Firebase ของคุณว่า <your-project> จำรหัสโปรเจ็กต์ของโปรเจ็กต์ Firebase
  3. คลิกสร้างโปรเจ็กต์

สำคัญ: ระบบจะตั้งชื่อโปรเจ็กต์ Firebase ว่า <your-project> แต่ Firebase จะกำหนดรหัสโปรเจ็กต์ที่ไม่ซ้ำให้โดยอัตโนมัติในรูปแบบ <your-project>-1234 ตัวระบุที่ไม่ซ้ำกันนี้คือวิธีระบุโปรเจ็กต์จริง (รวมถึงใน CLI) ขณะที่ <your-project> เป็นเพียงชื่อที่แสดง

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

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

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

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

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

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

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

หากต้องการเปิดใช้การลงชื่อเข้าใช้ Google ให้ทำดังนี้

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

เปิดใช้ Cloud Firestore

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

เปิดใช้ Cloud Storage

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

  1. ในส่วนสร้างของคอนโซล Firebase ให้คลิกพื้นที่เก็บข้อมูล
  2. หากไม่มีปุ่มเริ่มต้นใช้งาน แสดงว่ามีพื้นที่เก็บข้อมูลระบบคลาวด์อยู่แล้ว

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

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

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

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

อินเทอร์เฟซบรรทัดคำสั่ง (CLI) ของ Firebase ช่วยให้คุณใช้โฮสติ้งของ Firebase เพื่อแสดงเว็บแอปภายในเครื่อง รวมถึงทำให้เว็บแอปใช้งานได้ในโปรเจ็กต์ 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 แก้ไขฟังก์ชันหน้าการเข้าสู่ระบบเพื่อให้หน้าตรวจสอบสถานะการตรวจสอบสิทธิ์ของผู้ใช้ขณะโหลด

กำลังแทรกการตรวจสอบสิทธิ์ 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)
}

การอ่านข้อมูลเป็นแบบที่สังเกตได้

เนื่องจากโพสต์และป้ายจอดรถระหว่างทางสามารถแก้ไขได้หลังจากสร้าง การใช้ออบเจ็กต์เอกสารเป็นข้อมูลที่สังเกตได้จึงจะมีประโยชน์มากกว่าสำหรับการสมัครรับข้อมูลการเปลี่ยนแปลงที่เกิดขึ้น ฟังก์ชันนี้ให้บริการโดยฟังก์ชัน 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 ในบริการ 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 ในเครื่องมือสร้างเพื่อรับอาร์เรย์ที่สังเกตได้ของการเดินทางทั้งหมด

ในกรณีที่ต้องการเฉพาะการเดินทางของผู้ใช้ปัจจุบัน ให้ใช้ฟังก์ชัน query

วิธีอื่นๆ ในการดูแลความปลอดภัย ได้แก่ การใช้กฎความปลอดภัย หรือการใช้ Cloud Functions กับ Firestore ตามที่ได้อธิบายไว้ในขั้นตอนที่ไม่บังคับด้านล่าง

จากนั้น เพียงเรียกใช้ฟังก์ชันที่ใช้งานใน TravelService

async  createTravel(userId: String) {
	this.travelService.addEmptyTravel(userId);
}

deleteTravel(travelId: String) {
	this.travelService.deleteData(`travels/${travelId}`)
}

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

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

มาใช้ฟังก์ชันใน 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 แล้ว ตอนนี้คุณจะเข้าถึงข้อมูลและการวิเคราะห์ทั้งหมดได้ในคอนโซล Firebase

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

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

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

ใน 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 และพื้นที่เก็บข้อมูลมีสิทธิ์แบบเปิดสำหรับอ่านและเขียน แต่คุณไม่ต้องการให้ผู้อื่นเปลี่ยนแปลง โพสต์ คุณจะใช้กฎความปลอดภัยเพื่อจำกัดการเข้าถึงคอลเล็กชันและเอกสารได้

กฎ 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;
		}
	}
}