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

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

เกี่ยวกับ Codelab นี้

subjectอัปเดตล่าสุดเมื่อ พ.ค. 10, 2023
account_circleเขียนโดย Googler

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