Angular Firebase with Oxygen Not Included part 3

Phai Panda
5 min readMar 28, 2020

--

จาก part 2 ที่ผ่านมาเราได้เชื่อมต่อไปยัง Firebase Realtime Database แล้ว part นี้เราจะเชื่อมต่อไปยัง Cloud Firestore กันครับ

Cloud Firestroe implementation

ความเดิมตอนที่แล้วจ้า

:)

ทำทุกอย่างเช่นเดียวกับ 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

เรื่องชื่อไฟล์นี้ก็ยังไม่ต้องคิดมากนะครับ เอาตามความเหมาะสม

realtimedb & cloudfirestore structure

เอาล่ะไปผูก 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

หน้า trailers สำหรับรายการทั้งหมด ของ Realtime Database

โปรดสังเกตที่ address bar หรือ URL ซึ่งเป็นไปตามที่เราได้ config เอาไว้ที่ AppRoutingModule

:)

พักหายใจก่อนน้า~

:)

ต่อไปเป็นส่วนของ Cloud Firestore เชื่อว่าใครที่ทำส่วนของ Realtime Database ได้แล้ว ส่วนของ Cloud Firestore นี้ก็แทบจะไม่ต่างกันเลย

Cloud Firestore

อันดับแรกครับ ตรงไปยัง Friebase เหมือนที่ทำกับ Realtime Database เราต้องไปเปิดบริการการใช้งาน Cloud Firestore เสียก่อน

open cloud firestore

จากนั้นเลือก Start in test mode

select Start in test mode

จากนั้นเลือก location (นี่น่าจะเป็นตัวเลือกหนึ่งที่ตอบเรื่องความเร็ว) ก็ให้เลือกที่คิดว่าใกล้เราที่สุด บอกก่อนนะว่าเลือกแล้วเลือกเลยเปลี่ยนแปลงไม่ได้

ผมเลือกเป็น asia-south1 แล้วกัน

select location you want
Mumbai อินเดียตะวันตก

เมื่อพร้อมแล้วเราก็มาเขียนโค้ดกัน

เปิดไฟล์ 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

จะได้

create trailer model

จากนั้นย้าย Trailer มาไว้ที่นี่

Trailer class

ยิ่งไปกว่านั้น เดิมที่ให้ 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>

ผลลัพธ์

สักพัก loading… ก็จะหายไปเอง

เยี่ยมมาก!

เราทำได้เห็นไหม

:)

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

ยอดมาก เราตามกันทันแล้ว (หรือแซงผมไปแล้วก็ได้ ฮ่า)

เป็นอันว่าผมจะไม่นำเสนอส่วนที่เหลือดังกล่าวนี้นะครับ จะทำแล้วนำผลลัพธ์มาให้ชมเลย

ผลลัพธ์ของหน้านี้จะเป็นแบบนี้ครับ

cloud firestore trailers form

ขอนิดเดียวคือการจะนำของขึ้น 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

ผลลัพธ์

our result view
our result db on cloud firestore

:)

source code ทั้งหมดยังต้องการอยู่ไหม? คงไม่ต้องเน๊อะ ทำกันได้อยู่แล้ว

:)

เป็นอันว่าเรียบร้อย สำเร็จไปด้วยดีทั้ง Realtime Database (เก่า) และ Cloud Firebase (ใหม่) เพื่อการขอดูและเพิ่มข้อมูลไปยังฐานข้อมูล

โอกาสถัดไปเราจะเพิ่มฟังก์ชัน ‘แก้ไข’ รายการหรือ Trailer ของเราครับ แล้วเจอกันใหม่น้า~

ความรู้เพิ่มเติม

สวัสดี Cloud Firestore http://www.somkiat.cc/hello-cloud-firestore/

--

--

Responses (1)