Angular Firebase with Oxygen Not Included part 3
จาก part 2 ที่ผ่านมาเราได้เชื่อมต่อไปยัง Firebase Realtime Database แล้ว part นี้เราจะเชื่อมต่อไปยัง Cloud Firestore กันครับ
ความเดิมตอนที่แล้วจ้า
:)
ทำทุกอย่างเช่นเดียวกับ part ที่ผ่านมา ดังนั้นผมขอปรับแต่งหน้าจอเราสักหน่อย ผมจะแบ่งเป็น 2 หน้าใหญ่ๆก่อน ได้แก่หน้าที่เชื่อมต่อกับ Realtime Database และหน้าที่เชื่อมต่อไปยัง Cloud Firestore ทั้งสองหน้านี้ฟังก์ชันการทำงานไม่ต่างกันครับ จะต่างก็แค่ข้อมูลที่รับเข้าเท่านั้น (หรือจะให้รับเข้าเหมือนกันก็ได้นะ)
เพื่อนๆมือใหม่อย่าว่าผมไปเร็ว พื้นฐาน Angular และ Observable เพื่อนๆก็ต้องทำความรู้จักมาบ้าง อาจไม่ลึกมาก แต่ผมจะนำพวกมันมาประกอบร่างให้ดู พวกเราจะได้เห็นภาพรวมของการโปรแกรมมิ่งทั้งหมดและนั่นคือความตั้งใจของผม
Structure
โครงสร้างของโปรเจกต์เราจะออกแบบอย่างนี้
หน้าแรก (home page) — ให้เลือกระหว่างหน้า Realtime Database หรือหน้า Cloud Firestore
หน้า Realtime Database จะมีหน้า
- trailers สำหรับรายการทั้งหมด
- trailers/:id/edit สำหรับแก้ไขรายการตาม id
- และที่ trailers จะสามารถเพิ่มและลบรายการได้โดยแสดงเป็น popup
หน้า Cloud Firestore จะมีหน้า
- trailers สำหรับรายการทั้งหมด
- trailers/:id/edit สำหรับแก้ไขรายการตาม id
- และที่ trailers จะสามารถเพิ่มและลบรายการได้โดยแสดงเป็น popup
เข้าใจตรงกันแล้วนะ ดังนั้นมาสร้างกันเลย!
ng g c components/realtimedb-trailersng g c components/realtimedb-trailers-edit
และ
ng g c components/cloudfirestore-trailersng g c components/cloudfirestore-trailers-edit
เรื่องชื่อไฟล์นี้ก็ยังไม่ต้องคิดมากนะครับ เอาตามความเหมาะสม
เอาล่ะไปผูก router ก่อน เพื่อให้แต่ละหน้าสามารถเชื่อมถึงกันได้
Links
เปิดไฟล์ app-routing.module.ts หรือถ้าลืมสร้างก็สร้างขึ้นมาเสียตอนนี้ ไฟล์นี้เป็น module ที่ลงทะเบียน RouterModule เอาไว้ ทำหน้าที่เชื่อมโยงแต่ละหน้าหรือแต่ละ component (of content) เข้าด้วยกัน อ่านเพิ่มเติมได้ที่นี่
import { NgModule } from '@angular/core';import { RouterModule, Routes } from '@angular/router';const routes: Routes = [];@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule]})export class AppRoutingModule { }
และ AppRoutingModule นี้จะต้องถูกนำไปลงทะเบียนอีกทีที่ AppModule มันจึงจะทำงานได้
จากโค้ดข้างต้น RouterModule ต้องการ argument ชื่อ routes โดยที่ค่าของมันเป็น array ของ Route interface
เบื้องต้นให้ Route ใดๆประกอบด้วย path และ component ต่อไปนี้
const routes: Routes = [ { path: 'realtimedb/trailers', component: RealtimedbTrailersComponent }, { path: 'realtimedb/trailers/:id/edit', component: RealtimedbTrailersEditComponent }, { path: 'cloudfirestore/trailers', component: CloudfirestoreTrailersComponent }, { path: 'cloudfirestore/trailers/:id/edit', component: CloudfirestoreTrailersEditComponent },];
โค้ดปัจจุบันแสดงรายการทั้งหมด (trailer list) ที่ไฟล์ app.component.ts และ app.component.html ให้ย้ายไปแสดงที่ realtimedb-trailers.component.ts และ realtimedb-trailers.component.html ตามลำดับ
สุดท้ายจึงย้าย css จาก app.component.css ไปยัง realtimedb-trailers.component.css ด้วย
ข้อสังเกต
- ที่ผ่านมาคลาส AppComponent จะมีตัวแปรชื่อ title ซึ่งถูกสร้างขึ้นตั้งแต่แรกเริ่ม ประกาศไว้แต่ยังไม่ถูกนำมาใช้งาน
- หากเราใช้กลไกการ import แบบอัตโนมัติของ VS Code การ import คลาส AngularFireDatabase หากผิดพลาดลักษณะนี้ให้ตัดคำ database ตัวท้ายออก
import { AngularFireDatabase } from
'@angular/fire/database/database'; <-- ตัดตัวที่เกินออก
ไฟล์ app.component.html เพิ่มการแสดงผลตัวแปร title และ link นำทาง
<h1>{{title | uppercase}}</h1><div> <a>Realtime Database</a> | <a>Cloud Firestore</a></div>
จากนั้นใช้ routerLink ของ Angular เชื่อมระหว่างหน้านี้กับหน้า trailers สำหรับรายการทั้งหมด ของ Realtime Database พร้อมกับวาง <router-outlet></router-outlet>
ไว้ด้านล่างสุดของหน้า
<h1>{{title | uppercase}}</h1><div> <a routerLink="realtimedb/trailers">Realtime Database</a> | <a>Cloud Firestore</a></div><router-outlet></router-outlet>
ผลลัพธ์ที่ได้
หน้าแรก
คลิก Realtime Database
โปรดสังเกตที่ address bar หรือ URL ซึ่งเป็นไปตามที่เราได้ config เอาไว้ที่ AppRoutingModule
:)
พักหายใจก่อนน้า~
:)
ต่อไปเป็นส่วนของ Cloud Firestore เชื่อว่าใครที่ทำส่วนของ Realtime Database ได้แล้ว ส่วนของ Cloud Firestore นี้ก็แทบจะไม่ต่างกันเลย
Cloud Firestore
อันดับแรกครับ ตรงไปยัง Friebase เหมือนที่ทำกับ Realtime Database เราต้องไปเปิดบริการการใช้งาน Cloud Firestore เสียก่อน
จากนั้นเลือก Start in test mode
จากนั้นเลือก location (นี่น่าจะเป็นตัวเลือกหนึ่งที่ตอบเรื่องความเร็ว) ก็ให้เลือกที่คิดว่าใกล้เราที่สุด บอกก่อนนะว่าเลือกแล้วเลือกเลยเปลี่ยนแปลงไม่ได้
ผมเลือกเป็น asia-south1 แล้วกัน
เมื่อพร้อมแล้วเราก็มาเขียนโค้ดกัน
เปิดไฟล์ app.module.ts เพื่อลงทะเบียน AngularFirestoreModule เพิ่ม
...import { AngularFirestoreModule } from '@angular/fire/firestore';import 'firebase/firestore';...
และ
imports: [... AngularFirestoreModule,],
เปิดไฟล์ cloudfirestore-trailers.component.ts เรียกใช้ AngularFirestore
constructor( private firestore: AngularFirestore,) { }
ข้อสังเกต
- หากเราใช้กลไกการ import แบบอัตโนมัติของ VS Code การ import คลาส AngularFirestore หากผิดพลาดลักษณะนี้ให้ตัดคำ firestore ตัวท้ายออก
import { AngularFirestore } from
'@angular/fire/firestore/firestore'; <-- ตัดตัวที่เกินออก
Connect to Cloud Firestore
เมธอด ngOnInit ของคลาส CloudfirestoreTrailersComponent จะทำงานหลังจาก DOM ถูก render แล้ว เราจะให้ตัวแปร firestore เชื่อมต่อไปยัง Cloud Firestore
ngOnInit(): void { this.trailers$ = this.firestore.collection('trailers').valueChanges();}
การเชื่อมต่อเกิดขึ้น และค้นหา collection ชื่อ trailers (แบบเดียวกับ Realtime Database แต่คนละฐานข้อมูล) หากทำถูกต้องจะไม่พบ errror ใดๆ
ค่าที่ได้คือ array ของ Trailer และเราจะประกาศตัวแปรชื่อ trailers$ รับไป
...trailers$: Observable<Trailer[]>;...
นำไปวาดที่ไฟล์ cloudfirestore-trailers.component.html โดยจะคัดลอกมาจากไฟล์ realtimedb-trailers.component.html เลยก็ได้ (ก็มันเหมือนกันเลย) ดังนี้
<div *ngIf="trailers$ | async as trailers; else loading"> <div *ngFor="let t of trailers" class="trailer m-1 border rounded"> <iframe width="320" height="180" [src]="trustUrl(t.url)" frameborder="0"allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <div class="p-2">{{t.name}}</div> </div></div><ng-template #loading>loading...</ng-template>
อย่าลืมเมธอด trustUrl ก็ให้คัดลอกมาด้วย
import { DomSanitizer } from '@angular/platform-browser';...constructor( private firestore: AngularFirestore, private sanitizer: DomSanitizer,) { }
และ
trustUrl(url: string) { return this.sanitizer.bypassSecurityTrustResourceUrl(url);}
เกือบจะเรียบร้อยแล้ว โปรดสังเกตว่า Trailer[]
ที่อยู่กับ Observable
ตอนประกาศ trailers$
นั้นตัวจริงมันอยู่ที่ไฟล์ app.component.ts หรือหากเพื่อนๆได้คัดลอกมาแล้วก็ให้ผ่านส่วนนี้ไป ทว่าส่วนตัวอยากให้แยกมันออกมา เขียนเป็น models ไว้ต่างหาก แบบนี้ครับ
ng g class models/trailer
จะได้
จากนั้นย้าย Trailer
มาไว้ที่นี่
ยิ่งไปกว่านั้น เดิมที่ให้ Trailer เป็น interface ขอเปลี่ยนไปใช้ class แทน เพราะจากความคิด OOP เท่ากับว่า Trailer เป็นวัตถุ (object) ที่สมควรมีลักษณะของตนเอง
ตรวจสอบให้เรียบร้อยว่าได้ import ทุกอย่างถูกต้อง (โดยเฉพาะคลาส Trailer ที่เพิ่งย้ายมานี้)
ฮั่นแน่ ใครบางคนพบ error แล้ว เพราะ TypeScript ไม่ยอมให้บรรทัดนี้ผ่านใช่ไหม
this.trailers$ = this.firestore.collection('trailers').valueChanges();
จะต้องเปลี่ยนเป็น
this.trailers$ = this.firestore.collection<Trailer>('trailers').valueChanges();
Link
เชื่อมโยงระหว่างหน้าแรกกับหน้า cloudfirestore-trailers.component.html นี้
เปิดไฟล์ app.component.html
เพิ่ม routerLink แก่ลิงก์ Cloud Firestore
<h1>{{title | uppercase}}</h1><div><a routerLink="realtimedb/trailers">Realtime Database</a>| <a routerLink="cloudfirestore/trailers">Cloud Firestore</a></div><router-outlet></router-outlet>
ผลลัพธ์
เยี่ยมมาก!
เราทำได้เห็นไหม
:)
ผมเชื่อนะว่าเพื่อนคนไหนทำตามมาได้ขนาดนี้ก็น่าจะเข้าใจสิ่งที่ผมต้องการสื่อแทบทั้งหมดแล้ว โดยเฉพาะอย่างยิ่งตัวโค้ดที่จะเขียนถัดจากนี้ ก็จะทำฟอร์มขึ้นมา จากนั้นก็ให้รายละเอียดเหมือนที่ทำกับ Realtime Database นั่นแหละ
ยอดมาก เราตามกันทันแล้ว (หรือแซงผมไปแล้วก็ได้ ฮ่า)
เป็นอันว่าผมจะไม่นำเสนอส่วนที่เหลือดังกล่าวนี้นะครับ จะทำแล้วนำผลลัพธ์มาให้ชมเลย
ผลลัพธ์ของหน้านี้จะเป็นแบบนี้ครับ
ขอนิดเดียวคือการจะนำของขึ้น Cloud Firestore อย่างเมธอด onClickedAdd นั้นสำหรับ collection จะใช้เมธอด add
onClickedAdd() { if (this.form.invalid) { return; } const trailer: Trailer = { name: this.name.value.trim(), url: this.url.value, }; this.firestore.collection<Trailer>('trailers').add(trailer);}
อ่านรายละเอียดที่นี่
ผมจะนำ 2 ลิงก์นี้เพิ่มไปยังในฐานข้อมูล
https://www.youtube.com/embed/c_Vy3A_cAwY
https://www.youtube.com/embed/ggU8PngPRPo
ผลลัพธ์
:)
source code ทั้งหมดยังต้องการอยู่ไหม? คงไม่ต้องเน๊อะ ทำกันได้อยู่แล้ว
:)
เป็นอันว่าเรียบร้อย สำเร็จไปด้วยดีทั้ง Realtime Database (เก่า) และ Cloud Firebase (ใหม่) เพื่อการขอดูและเพิ่มข้อมูลไปยังฐานข้อมูล
โอกาสถัดไปเราจะเพิ่มฟังก์ชัน ‘แก้ไข’ รายการหรือ Trailer ของเราครับ แล้วเจอกันใหม่น้า~
ความรู้เพิ่มเติม
สวัสดี Cloud Firestore http://www.somkiat.cc/hello-cloud-firestore/