1. บทนำ
อัปเดตล่าสุด: 11-09-2020
สิ่งที่คุณจะสร้าง
ใน Codelab นี้ เราจะสร้างกระดานคัมบังบนเว็บด้วย Angular และ Firebase แอปสุดท้ายของเราจะมีงาน 3 หมวดหมู่ ได้แก่ งานที่ค้าง งานที่กำลังดำเนินการ และงานที่เสร็จแล้ว เราจะสร้าง ลบงาน และโอนงานจากหมวดหมู่หนึ่งไปยังอีกหมวดหมู่หนึ่งได้โดยใช้การลากและวาง
เราจะพัฒนาอินเทอร์เฟซผู้ใช้โดยใช้ Angular และใช้ Firestore เป็นที่เก็บข้อมูลแบบถาวร ในตอนท้ายของโค้ดแล็บ เราจะทําให้แอปใช้งานได้กับโฮสติ้งของ Firebase โดยใช้ Angular CLI
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้วัสดุ Angular และ CDK
- วิธีเพิ่มการผสานรวม Firebase ลงในแอป Angular
- วิธีเก็บข้อมูลถาวรไว้ใน Firestore
- วิธีทําให้แอปใช้งานได้ใน Firebase Hosting โดยใช้ Angular CLI ด้วยคําสั่งเดียว
สิ่งที่ต้องมี
Codelab นี้มีสมมติฐานว่าคุณมีบัญชี Google และมีความเข้าใจพื้นฐานเกี่ยวกับ Angular และ Angular CLI
มาเริ่มกันเลย
2. การสร้างโปรเจ็กต์ใหม่
ก่อนอื่นมาสร้างพื้นที่ทำงาน Angular ใหม่กัน
ng new kanban-fire
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
ขั้นตอนนี้อาจใช้เวลาสักครู่ Angular CLI จะสร้างโครงสร้างโปรเจ็กต์และติดตั้งการขึ้นต่อกันทั้งหมด เมื่อกระบวนการติดตั้งเสร็จสมบูรณ์ ให้ไปที่ไดเรกทอรี kanban-fire
แล้วเริ่มเซิร์ฟเวอร์การพัฒนาของ Angular CLI โดยทำดังนี้
ng serve
เปิด http://localhost:4200 แล้วคุณจะเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้
ในโปรแกรมแก้ไข ให้เปิด src/app/app.component.html
แล้วลบเนื้อหาทั้งหมด เมื่อกลับไปที่ http://localhost:4200 คุณจะเห็นหน้าว่าง
3. การเพิ่ม Material และ CDK
Angular มาพร้อมกับการติดตั้งใช้งานคอมโพเนนต์อินเทอร์เฟซผู้ใช้ที่สอดคล้องกับ Material Design ซึ่งเป็นส่วนหนึ่งของแพ็กเกจ @angular/material
หนึ่งในการอ้างอิงของ @angular/material
คือ ชุดพัฒนาคอมโพเนนต์หรือ CDK CDK มีองค์ประกอบพื้นฐาน เช่น ยูทิลิตี a11y, การลากและวาง และการวางซ้อน เราเผยแพร่ CDK ในแพ็กเกจ @angular/cdk
วิธีเพิ่มเนื้อหาลงในการเรียกใช้แอป
ng add @angular/material
คำสั่งนี้จะขอให้คุณเลือกธีม หากต้องการใช้รูปแบบการพิมพ์แบบ Material ทั่วโลก และหากต้องการตั้งค่าภาพเคลื่อนไหวของเบราว์เซอร์สำหรับ Angular Material เลือก "คราม/ชมพู" เพื่อให้ได้ผลลัพธ์เหมือนในโค้ดแล็บนี้ แล้วตอบว่า "ใช่" สำหรับคำถาม 2 ข้อสุดท้าย
คำสั่ง ng add
จะติดตั้ง @angular/material
, ทรัพยากร Dependency และนำเข้า BrowserAnimationsModule
ใน AppModule
ในขั้นตอนถัดไป เราจะเริ่มใช้คอมโพเนนต์ที่โมดูลนี้มีให้ได้
ก่อนอื่น มาเพิ่มแถบเครื่องมือและไอคอนลงใน AppComponent
กัน เปิด app.component.html
แล้วเพิ่มมาร์กอัปต่อไปนี้
src/app/app.component.html
<mat-toolbar color="primary">
<mat-icon>local_fire_department</mat-icon>
<span>Kanban Fire</span>
</mat-toolbar>
ในที่นี้ เราจะเพิ่มแถบเครื่องมือโดยใช้สีหลักของธีม Material Design และภายในแถบเครื่องมือ เราจะใช้ไอคอน local_fire_depeartment
ข้างป้ายกำกับ "Kanban Fire" หากดูคอนโซลตอนนี้ คุณจะเห็นว่า Angular แสดงข้อผิดพลาด 2-3 รายการ หากต้องการแก้ไข ให้ตรวจสอบว่าคุณได้เพิ่มการนำเข้าต่อไปนี้ลงใน AppModule
แล้ว
src/app/app.module.ts
...
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
MatToolbarModule,
MatIconModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
เนื่องจากเราใช้แถบเครื่องมือและไอคอน Angular Material จึงต้องนำเข้าโมดูลที่เกี่ยวข้องใน AppModule
ตอนนี้คุณควรเห็นข้อมูลต่อไปนี้บนหน้าจอ
ไม่เลวเลยใช่ไหมกับโค้ด HTML เพียง 4 บรรทัดและการนำเข้า 2 รายการ
4. การแสดงภาพงาน
ในขั้นตอนถัดไป เรามาสร้างคอมโพเนนต์ที่ใช้แสดงภาพงานในกระดานคัมบังกัน
ไปที่ไดเรกทอรี src/app
แล้วเรียกใช้คำสั่ง CLI ต่อไปนี้
ng generate component task
คำสั่งนี้จะสร้าง TaskComponent
และเพิ่มการประกาศลงใน AppModule
สร้างไฟล์ชื่อ task.ts
ในไดเรกทอรี task
เราจะใช้ไฟล์นี้เพื่อกำหนดอินเทอร์เฟซของงานในกระดานคัมบัง งานแต่ละอย่างจะมีช่อง id
, title
และ description
ที่ไม่บังคับซึ่งเป็นสตริงทั้งหมด
src/app/task/task.ts
export interface Task {
id?: string;
title: string;
description: string;
}
ตอนนี้มาอัปเดต task.component.ts
กัน เราต้องการให้ TaskComponent
รับออบเจ็กต์ประเภท Task
เป็นอินพุต และต้องการให้สามารถส่งเอาต์พุต "edit
" ได้
src/app/task/task.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Task } from './task';
@Component({
selector: 'app-task',
templateUrl: './task.component.html',
styleUrls: ['./task.component.css']
})
export class TaskComponent {
@Input() task: Task | null = null;
@Output() edit = new EventEmitter<Task>();
}
แก้ไขเทมเพลตของ TaskComponent
เปิด task.component.html
แล้วแทนที่เนื้อหาด้วย HTML ต่อไปนี้
src/app/task/task.component.html
<mat-card class="item" *ngIf="task" (dblclick)="edit.emit(task)">
<h2>{{ task.title }}</h2>
<p>
{{ task.description }}
</p>
</mat-card>
โปรดสังเกตว่าตอนนี้เราได้รับข้อผิดพลาดในคอนโซลแล้ว
'mat-card' is not a known element:
1. If 'mat-card' is an Angular component, then verify that it is part of this module.
2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.ng
ในเทมเพลตด้านบน เราใช้คอมโพเนนต์ mat-card
จาก @angular/material
แต่เรายังไม่ได้นำเข้าโมดูลที่เกี่ยวข้องในแอป หากต้องการแก้ไขข้อผิดพลาดจากด้านบน เราต้องนำเข้า MatCardModule
ใน AppModule
ดังนี้
src/app/app.module.ts
...
import { MatCardModule } from '@angular/material/card';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
MatCardModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
จากนั้นเราจะสร้างงาน 2-3 งานใน AppComponent
และแสดงภาพงานเหล่านั้นโดยใช้ TaskComponent
ใน AppComponent
ให้กำหนดอาร์เรย์ที่ชื่อ todo
และเพิ่มงาน 2 อย่างลงในอาร์เรย์ดังกล่าว
src/app/app.component.ts
...
import { Task } from './task/task';
@Component(...)
export class AppComponent {
todo: Task[] = [
{
title: 'Buy milk',
description: 'Go to the store and buy milk'
},
{
title: 'Create a Kanban app',
description: 'Using Firebase and Angular create a Kanban app!'
}
];
}
ตอนนี้ ให้เพิ่มคำสั่ง *ngFor
ต่อไปนี้ที่ด้านล่างของ app.component.html
src/app/app.component.html
<app-task *ngFor="let task of todo" [task]="task"></app-task>
เมื่อเปิดเบราว์เซอร์ คุณควรเห็นสิ่งต่อไปนี้
5. การใช้การลากและวางสำหรับงาน
ตอนนี้เราพร้อมที่จะเข้าสู่ช่วงสนุกๆ แล้ว มาสร้างเลนว่าย 3 เลนสำหรับสถานะต่างๆ 3 สถานะที่งานอาจอยู่ และใช้ Angular CDK เพื่อใช้ฟังก์ชันลากและวางกัน
ใน app.component.html
ให้นำคอมโพเนนต์ app-task
ออกด้วยคำสั่ง *ngFor
ที่ด้านบน แล้วแทนที่ด้วย
src/app/app.component.html
<div class="content-wrapper">
<div class="container-wrapper">
<div class="container">
<h2>Backlog</h2>
<mat-card
cdkDropList
id="todo"
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList, inProgressList]"
(cdkDropListDropped)="drop($event)"
class="list">
<p class="empty-label" *ngIf="todo.length === 0">Empty list</p>
<app-task (edit)="editTask('todo', $event)" *ngFor="let task of todo" cdkDrag [task]="task"></app-task>
</mat-card>
</div>
<div class="container">
<h2>In progress</h2>
<mat-card
cdkDropList
id="inProgress"
#inProgressList="cdkDropList"
[cdkDropListData]="inProgress"
[cdkDropListConnectedTo]="[todoList, doneList]"
(cdkDropListDropped)="drop($event)"
class="list">
<p class="empty-label" *ngIf="inProgress.length === 0">Empty list</p>
<app-task (edit)="editTask('inProgress', $event)" *ngFor="let task of inProgress" cdkDrag [task]="task"></app-task>
</mat-card>
</div>
<div class="container">
<h2>Done</h2>
<mat-card
cdkDropList
id="done"
#doneList="cdkDropList"
[cdkDropListData]="done"
[cdkDropListConnectedTo]="[todoList, inProgressList]"
(cdkDropListDropped)="drop($event)"
class="list">
<p class="empty-label" *ngIf="done.length === 0">Empty list</p>
<app-task (edit)="editTask('done', $event)" *ngFor="let task of done" cdkDrag [task]="task"></app-task>
</mat-card>
</div>
</div>
</div>
มีหลายอย่างเกิดขึ้นที่นี่ มาดูแต่ละส่วนของข้อมูลโค้ดนี้ทีละขั้นตอนกัน นี่คือโครงสร้างระดับบนสุดของเทมเพลต
src/app/app.component.html
...
<div class="container-wrapper">
<div class="container">
<h2>Backlog</h2>
...
</div>
<div class="container">
<h2>In progress</h2>
...
</div>
<div class="container">
<h2>Done</h2>
...
</div>
</div>
ในที่นี้ เราสร้าง div
ที่ครอบคลุมเลนว่ายน้ำทั้ง 3 เลน โดยมีชื่อคลาสเป็น "container-wrapper
" เลนว่ายน้ำแต่ละเลนมีชื่อคลาสเป็น "container
" และมีชื่ออยู่ภายในแท็ก h2
ตอนนี้มาดูโครงสร้างของเลนว่ายแรกกัน
src/app/app.component.html
...
<div class="container">
<h2>Backlog</h2>
<mat-card
cdkDropList
id="todo"
#todoList="cdkDropList"
[cdkDropListData]="todo"
[cdkDropListConnectedTo]="[doneList, inProgressList]"
(cdkDropListDropped)="drop($event)"
class="list"
>
<p class="empty-label" *ngIf="todo.length === 0">Empty list</p>
<app-task (edit)="editTask('todo', $event)" *ngFor="let task of todo" cdkDrag [task]="task"></app-task>
</mat-card>
</div>
...
ก่อนอื่น เราจะกำหนด Swimlane เป็น mat-card
ซึ่งใช้คำสั่ง cdkDropList
เราใช้ mat-card
เนื่องจากสไตล์ที่คอมโพเนนต์นี้มีให้ ซึ่งในภายหลัง cdkDropList
จะช่วยให้เราวางงานภายในองค์ประกอบได้ นอกจากนี้ เรายังตั้งค่าอินพุต 2 รายการต่อไปนี้ด้วย
cdkDropListData
- อินพุตของรายการแบบเลื่อนลงที่ช่วยให้เราระบุอาร์เรย์ข้อมูลได้cdkDropListConnectedTo
- การอ้างอิงถึงcdkDropList
อื่นๆ ที่cdkDropList
ปัจจุบันเชื่อมต่ออยู่ การตั้งค่าอินพุตนี้จะระบุรายการอื่นๆ ที่เราสามารถวางรายการลงไปได้
นอกจากนี้ เรายังต้องการจัดการเหตุการณ์การวางโดยใช้เอาต์พุต cdkDropListDropped
เมื่อ cdkDropList
ส่งเอาต์พุตนี้ออกมา เราจะเรียกใช้เมธอด drop
ที่ประกาศไว้ภายใน AppComponent
และส่งเหตุการณ์ปัจจุบันเป็นอาร์กิวเมนต์
โปรดทราบว่าเรายังระบุ id
ที่จะใช้เป็นตัวระบุสำหรับคอนเทนเนอร์นี้ และชื่อ class
เพื่อให้เราจัดรูปแบบได้ ตอนนี้เรามาดูเนื้อหาของเด็กๆ ใน mat-card
กัน องค์ประกอบ 2 อย่างที่เรามี ได้แก่
- ย่อหน้าซึ่งเราใช้เพื่อแสดงข้อความ "รายการว่าง" เมื่อไม่มีรายการใน
todo
รายการ - คอมโพเนนต์
app-task
โปรดทราบว่าในที่นี้เรากำลังจัดการเอาต์พุตedit
ที่เราประกาศไว้แต่เดิมโดยการเรียกใช้เมธอดeditTask
ด้วยชื่อของรายการและออบเจ็กต์$event
ซึ่งจะช่วยให้เราแทนที่งานที่แก้ไขจากรายการที่ถูกต้องได้ จากนั้นเราจะวนซ้ำในtodo
list เหมือนที่เราทำด้านบนและส่งอินพุตtask
แต่ครั้งนี้เราจะเพิ่มคำสั่งcdkDrag
ด้วย ซึ่งจะทำให้ลากงานแต่ละรายการได้
หากต้องการให้ทุกอย่างทำงานได้ เราต้องอัปเดต app.module.ts
และรวมการนำเข้าไปยัง DragDropModule
src/app/app.module.ts
...
import { DragDropModule } from '@angular/cdk/drag-drop';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
DragDropModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
นอกจากนี้ เรายังต้องประกาศอาร์เรย์ inProgress
และ done
พร้อมกับเมธอด editTask
และ drop
ด้วย
src/app/app.component.ts
...
import { CdkDragDrop, transferArrayItem } from '@angular/cdk/drag-drop';
@Component(...)
export class AppComponent {
todo: Task[] = [...];
inProgress: Task[] = [];
done: Task[] = [];
editTask(list: string, task: Task): void {}
drop(event: CdkDragDrop<Task[]>): void {
if (event.previousContainer === event.container) {
return;
}
if (!event.container.data || !event.previousContainer.data) {
return;
}
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
);
}
}
โปรดสังเกตว่าในวิธี drop
เราจะตรวจสอบก่อนว่าเรากำลังวางในรายการเดียวกันกับที่งานมาจาก หากเป็นเช่นนั้น เราจะกลับมาทันที ไม่เช่นนั้น เราจะโอนงานปัจจุบันไปยังเลนว่ายน้ำปลายทาง
ผลลัพธ์ที่ได้ควรเป็นดังนี้
ตอนนี้คุณควรจะโอนรายการระหว่าง 2 รายการนี้ได้แล้ว
6. สร้างงานใหม่
ตอนนี้เรามาใช้ฟังก์ชันการทำงานสำหรับการสร้างงานใหม่กัน ในกรณีนี้ เราจะอัปเดตเทมเพลตของ AppComponent
ดังนี้
src/app/app.component.html
<mat-toolbar color="primary">
...
</mat-toolbar>
<div class="content-wrapper">
<button (click)="newTask()" mat-button>
<mat-icon>add</mat-icon> Add Task
</button>
<div class="container-wrapper">
<div class="container">
...
</div>
</div>
เราสร้างองค์ประกอบ div
ระดับบนสุดรอบ container-wrapper
และเพิ่มปุ่มที่มีไอคอน Material "add
" ข้างป้ายกำกับ "เพิ่มงาน" เราต้องใช้ Wrapper เพิ่มเติมเพื่อวางปุ่มไว้เหนือรายการเลนว่าย ซึ่งเราจะวางไว้ข้างกันในภายหลังโดยใช้ Flexbox เนื่องจากปุ่มนี้ใช้คอมโพเนนต์ปุ่ม Material เราจึงต้องนำเข้าโมดูลที่เกี่ยวข้องใน AppModule
ดังนี้
src/app/app.module.ts
...
import { MatButtonModule } from '@angular/material/button';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
MatButtonModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
ตอนนี้เรามาใช้ฟังก์ชันการเพิ่มงานใน AppComponent
กัน เราจะใช้กล่องโต้ตอบ Material ในกล่องโต้ตอบ เราจะมีแบบฟอร์มที่มี 2 ช่อง ได้แก่ ชื่อและคำอธิบาย เมื่อผู้ใช้คลิกปุ่ม "เพิ่มงาน" เราจะเปิดกล่องโต้ตอบ และเมื่อผู้ใช้ส่งแบบฟอร์ม เราจะเพิ่มงานที่สร้างขึ้นใหม่ลงในtodo
รายการ
มาดูการติดตั้งใช้งานฟังก์ชันนี้ในAppComponent
กัน
src/app/app.component.ts
...
import { MatDialog } from '@angular/material/dialog';
@Component(...)
export class AppComponent {
...
constructor(private dialog: MatDialog) {}
newTask(): void {
const dialogRef = this.dialog.open(TaskDialogComponent, {
width: '270px',
data: {
task: {},
},
});
dialogRef
.afterClosed()
.subscribe((result: TaskDialogResult|undefined) => {
if (!result) {
return;
}
this.todo.push(result.task);
});
}
}
เราประกาศตัวสร้างซึ่งเราจะแทรกคลาส MatDialog
ภายใน newTask
เราจะทำสิ่งต่อไปนี้
- เปิดกล่องโต้ตอบใหม่โดยใช้
TaskDialogComponent
ซึ่งเราจะกำหนดในอีกสักครู่ - ระบุว่าเราต้องการให้กล่องโต้ตอบมีความกว้าง
270px.
- ส่งงานที่ว่างเปล่าไปยังกล่องโต้ตอบเป็นข้อมูล ใน
TaskDialogComponent
เราจะอ้างอิงออบเจ็กต์ข้อมูลนี้ได้ - เราจะติดตามเหตุการณ์ปิดและเพิ่มงานจากออบเจ็กต์
result
ไปยังอาร์เรย์todo
หากต้องการให้การดำเนินการนี้ได้ผล เราต้องนำเข้า MatDialogModule
ใน AppModule
ก่อน โดยทำดังนี้
src/app/app.module.ts
...
import { MatDialogModule } from '@angular/material/dialog';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
MatDialogModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
ตอนนี้มาสร้าง TaskDialogComponent
กัน ไปที่ไดเรกทอรี src/app
แล้วเรียกใช้คำสั่งต่อไปนี้
ng generate component task-dialog
หากต้องการใช้ฟังก์ชันการทำงาน ให้เปิด src/app/task-dialog/task-dialog.component.html
ก่อน แล้วแทนที่เนื้อหาด้วย
src/app/task-dialog/task-dialog.component.html
<mat-form-field>
<mat-label>Title</mat-label>
<input matInput cdkFocusInitial [(ngModel)]="data.task.title" />
</mat-form-field>
<mat-form-field>
<mat-label>Description</mat-label>
<textarea matInput [(ngModel)]="data.task.description"></textarea>
</mat-form-field>
<div mat-dialog-actions>
<button mat-button [mat-dialog-close]="{ task: data.task }">OK</button>
<button mat-button (click)="cancel()">Cancel</button>
</div>
ในเทมเพลตด้านบน เราสร้างแบบฟอร์มที่มี 2 ช่องสำหรับ title
และ description
เราใช้คำสั่ง cdkFocusInput
เพื่อโฟกัสอินพุต title
โดยอัตโนมัติเมื่อผู้ใช้เปิดกล่องโต้ตอบ
โปรดสังเกตว่าภายในเทมเพลต เราอ้างอิงพร็อพเพอร์ตี้ data
ของคอมโพเนนต์ ซึ่งจะเป็น data
เดียวกันกับที่เราส่งไปยังเมธอด open
ของ dialog
ใน AppComponent
หากต้องการอัปเดตชื่อและคำอธิบายของงานเมื่อผู้ใช้เปลี่ยนเนื้อหาของช่องที่เกี่ยวข้อง เราจะใช้การเชื่อมโยงข้อมูลแบบ 2 ทางกับ ngModel
เมื่อผู้ใช้คลิกปุ่มตกลง เราจะแสดงผลลัพธ์ { task: data.task }
โดยอัตโนมัติ ซึ่งเป็นงานที่เราแก้ไขโดยใช้ช่องแบบฟอร์มในเทมเพลตด้านบน
ตอนนี้มาใช้ตัวควบคุมของคอมโพเนนต์กัน
src/app/task-dialog/task-dialog.component.ts
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Task } from '../task/task';
@Component({
selector: 'app-task-dialog',
templateUrl: './task-dialog.component.html',
styleUrls: ['./task-dialog.component.css'],
})
export class TaskDialogComponent {
private backupTask: Partial<Task> = { ...this.data.task };
constructor(
public dialogRef: MatDialogRef<TaskDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: TaskDialogData
) {}
cancel(): void {
this.data.task.title = this.backupTask.title;
this.data.task.description = this.backupTask.description;
this.dialogRef.close(this.data);
}
}
ใน TaskDialogComponent
เราจะแทรกการอ้างอิงไปยังกล่องโต้ตอบเพื่อให้ปิดได้ และยังแทรกค่าของผู้ให้บริการที่เชื่อมโยงกับโทเค็น MAT_DIALOG_DATA
ด้วย นี่คือออบเจ็กต์ข้อมูลที่เราส่งไปยังเมธอด open ใน AppComponent
ด้านบน นอกจากนี้ เรายังประกาศพร็อพเพอร์ตี้ส่วนตัว backupTask
ซึ่งเป็นสำเนาของงานที่เราส่งพร้อมกับออบเจ็กต์ข้อมูล
เมื่อผู้ใช้กดปุ่มยกเลิก เราจะคืนค่าพร็อพเพอร์ตี้ของ this.data.task
ที่อาจมีการเปลี่ยนแปลง และปิดกล่องโต้ตอบโดยส่ง this.data
เป็นผลลัพธ์
เราอ้างอิงถึงประเภท 2 ประเภทแต่ยังไม่ได้ประกาศ ได้แก่ TaskDialogData
และ TaskDialogResult
ใน src/app/task-dialog/task-dialog.component.ts
ให้เพิ่มการประกาศต่อไปนี้ที่ด้านล่างของไฟล์
src/app/task-dialog/task-dialog.component.ts
...
export interface TaskDialogData {
task: Partial<Task>;
enableDelete: boolean;
}
export interface TaskDialogResult {
task: Task;
delete?: boolean;
}
สิ่งสุดท้ายที่เราต้องทำก่อนที่จะพร้อมใช้งานฟังก์ชันนี้คือการนำเข้าโมดูล 2-3 โมดูลใน AppModule
src/app/app.module.ts
...
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
MatInputModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
เมื่อคลิกปุ่ม "เพิ่มงาน" คุณจะเห็นอินเทอร์เฟซผู้ใช้ต่อไปนี้
7. การปรับปรุงสไตล์ของแอป
เราจะปรับปรุงเลย์เอาต์ของแอปพลิเคชันโดยการปรับแต่งสไตล์เล็กน้อยเพื่อให้แอปพลิเคชันดูน่าสนใจยิ่งขึ้น เราต้องการวางเลนว่ายน้ำไว้ข้างกัน นอกจากนี้ เรายังต้องการปรับเปลี่ยนปุ่ม "เพิ่มงาน" และป้ายกำกับลิสต์ว่างเล็กน้อยด้วย
เปิด src/app/app.component.css
แล้วเพิ่มรูปแบบต่อไปนี้ที่ด้านล่าง
src/app/app.component.css
mat-toolbar {
margin-bottom: 20px;
}
mat-toolbar > span {
margin-left: 10px;
}
.content-wrapper {
max-width: 1400px;
margin: auto;
}
.container-wrapper {
display: flex;
justify-content: space-around;
}
.container {
width: 400px;
margin: 0 25px 25px 0;
}
.list {
border: solid 1px #ccc;
min-height: 60px;
border-radius: 4px;
}
app-new-task {
margin-bottom: 30px;
}
.empty-label {
font-size: 2em;
padding-top: 10px;
text-align: center;
opacity: 0.2;
}
ในข้อมูลโค้ดด้านบน เราจะปรับเลย์เอาต์ของแถบเครื่องมือและป้ายกำกับ นอกจากนี้ เรายังตรวจสอบว่าเนื้อหาวางแนวในแนวนอนโดยตั้งค่าความกว้างเป็น 1400px
และระยะขอบเป็น auto
จากนั้นเราใช้ Flexbox เพื่อวาง Swimlane ไว้ข้างกัน และสุดท้ายก็ปรับเปลี่ยนวิธีแสดงภาพงานและรายการที่ว่างเปล่า
เมื่อแอปโหลดซ้ำแล้ว คุณควรเห็นอินเทอร์เฟซผู้ใช้ต่อไปนี้
แม้ว่าเราจะปรับปรุงสไตล์ของแอปไปมากแล้ว แต่ก็ยังคงมีปัญหาที่น่ารำคาญเมื่อย้ายงานไปมา
เมื่อเริ่มลากงาน "ซื้อนม" เราจะเห็นการ์ด 2 ใบสำหรับงานเดียวกัน ได้แก่ การ์ดที่เรากำลังลากและการ์ดในแถบว่ายน้ำ Angular CDK มีชื่อคลาส CSS ที่เราใช้เพื่อแก้ไขปัญหานี้ได้
เพิ่มการลบล้างรูปแบบต่อไปนี้ที่ด้านล่างของ src/app/app.component.css
src/app/app.component.css
.cdk-drag-animating {
transition: transform 250ms;
}
.cdk-drag-placeholder {
opacity: 0;
}
ขณะที่เราลากองค์ประกอบ ฟีเจอร์ลากและวางของ Angular CDK จะโคลนองค์ประกอบนั้นและแทรกลงในตำแหน่งที่เราจะวางองค์ประกอบเดิม เราตั้งค่าพร็อพเพอร์ตี้ความทึบในคลาส cdk-drag-placeholder
ซึ่ง CDK จะเพิ่มลงในตัวยึดตำแหน่งเพื่อให้มั่นใจว่าองค์ประกอบนี้จะไม่ปรากฏ
นอกจากนี้ เมื่อเราวางองค์ประกอบ CDK จะเพิ่มคลาส cdk-drag-animating
หากต้องการแสดงภาพเคลื่อนไหวที่ราบรื่นแทนการสแนปองค์ประกอบโดยตรง เราจะกำหนดทรานซิชันที่มีระยะเวลา 250ms
นอกจากนี้ เรายังต้องการปรับเปลี่ยนรูปแบบของงานเล็กน้อยด้วย ใน task.component.css
เรามาตั้งค่าการแสดงผลขององค์ประกอบโฮสต์เป็น block
และตั้งค่าขอบกัน
src/app/task/task.component.css
:host {
display: block;
}
.item {
margin-bottom: 10px;
cursor: pointer;
}
8. การแก้ไขและลบงานที่มีอยู่
หากต้องการแก้ไขและนำงานที่มีอยู่ออก เราจะนำฟังก์ชันส่วนใหญ่ที่เราได้ติดตั้งใช้งานไปแล้วมาใช้ซ้ำ เมื่อผู้ใช้ดับเบิลคลิกงาน เราจะเปิด TaskDialogComponent
และป้อนข้อมูลในช่อง 2 ช่องในแบบฟอร์มด้วย title
และ description
ของงาน
นอกจากนี้ เราจะเพิ่มปุ่มลบในTaskDialogComponent
ด้วย เมื่อผู้ใช้คลิก เราจะส่งคำสั่งลบ ซึ่งจะไปสิ้นสุดที่ AppComponent
การเปลี่ยนแปลงเพียงอย่างเดียวที่เราต้องทำใน TaskDialogComponent
คือการเปลี่ยนแปลงในเทมเพลตของ TaskDialogComponent
src/app/task-dialog/task-dialog.component.html
<mat-form-field>
...
</mat-form-field>
<div mat-dialog-actions>
...
<button
*ngIf="data.enableDelete"
mat-fab
color="primary"
aria-label="Delete"
[mat-dialog-close]="{ task: data.task, delete: true }">
<mat-icon>delete</mat-icon>
</button>
</div>
ปุ่มนี้จะแสดงไอคอนลบสื่อการเรียนการสอน เมื่อผู้ใช้คลิก เราจะปิดกล่องโต้ตอบและส่งออบเจ็กต์ลิเทอรัล { task: data.task, delete: true }
เป็นผลลัพธ์ นอกจากนี้ โปรดสังเกตว่าเราทำให้ปุ่มเป็นวงกลมโดยใช้ mat-fab
ตั้งค่าสีเป็นสีหลัก และแสดงปุ่มเฉพาะเมื่อเปิดใช้การลบข้อมูลในกล่องโต้ตอบ
ส่วนที่เหลือของการติดตั้งใช้งานฟังก์ชันการแก้ไขและการลบจะอยู่ใน AppComponent
แทนที่เมธอด editTask
ด้วยเมธอดต่อไปนี้
src/app/app.component.ts
@Component({ ... })
export class AppComponent {
...
editTask(list: 'done' | 'todo' | 'inProgress', task: Task): void {
const dialogRef = this.dialog.open(TaskDialogComponent, {
width: '270px',
data: {
task,
enableDelete: true,
},
});
dialogRef.afterClosed().subscribe((result: TaskDialogResult|undefined) => {
if (!result) {
return;
}
const dataList = this[list];
const taskIndex = dataList.indexOf(task);
if (result.delete) {
dataList.splice(taskIndex, 1);
} else {
dataList[taskIndex] = task;
}
});
}
...
}
มาดูอาร์กิวเมนต์ของเมธอด editTask
กัน
- รายการประเภท
'done' | 'todo' | 'inProgress',
ซึ่งเป็นประเภทการรวมสตริงลิเทอรัลที่มีค่าที่สอดคล้องกับพร็อพเพอร์ตี้ที่เชื่อมโยงกับเลนแต่ละเลน - งานปัจจุบันที่เราต้องการแก้ไข
ในเนื้อหาของเมธอด เราจะเปิดอินสแตนซ์ของ TaskDialogComponent
ก่อน เนื่องจากเป็น data
เราจึงส่งออบเจ็กต์ลิเทอรัล ซึ่งระบุงานที่ต้องการแก้ไข และยังเปิดใช้ปุ่มแก้ไขในแบบฟอร์มโดยตั้งค่าพร็อพเพอร์ตี้ enableDelete
เป็น true
เมื่อได้รับผลลัพธ์จากกล่องโต้ตอบ เราจะจัดการ 2 สถานการณ์ต่อไปนี้
- เมื่อตั้งค่าแฟล็ก
delete
เป็นtrue
(เช่น เมื่อผู้ใช้กดปุ่มลบ) เราจะนำงานออกจากรายการที่เกี่ยวข้อง - หรือเราจะแทนที่งานในดัชนีที่ระบุด้วยงานที่เราได้รับจากผลลัพธ์ของกล่องโต้ตอบก็ได้
9. การสร้างโปรเจ็กต์ Firebase ใหม่
ตอนนี้มาสร้างโปรเจ็กต์ Firebase ใหม่กัน
- ไปที่ Firebase Console
- สร้างโปรเจ็กต์ใหม่ชื่อ "KanbanFire"
10. การเพิ่ม Firebase ลงในโปรเจ็กต์
ในส่วนนี้ เราจะผสานรวมโปรเจ็กต์กับ Firebase ทีม Firebase มีแพ็กเกจ @angular/fire
ซึ่งช่วยผสานรวมเทคโนโลยีทั้ง 2 อย่าง หากต้องการเพิ่มการรองรับ Firebase ลงในแอป ให้เปิดไดเรกทอรีรากของพื้นที่ทํางานแล้วเรียกใช้คําสั่งต่อไปนี้
ng add @angular/fire
คำสั่งนี้จะติดตั้งแพ็กเกจ @angular/fire
และถามคำถามคุณ 2-3 ข้อ ในเทอร์มินัล คุณควรเห็นข้อความคล้ายกับข้อความต่อไปนี้
ในระหว่างนี้ การติดตั้งจะเปิดหน้าต่างเบราว์เซอร์เพื่อให้คุณตรวจสอบสิทธิ์ด้วยบัญชี Firebase ได้ สุดท้าย ระบบจะขอให้คุณเลือกโปรเจ็กต์ Firebase และสร้างไฟล์บางอย่างในดิสก์
ต่อไปเราต้องสร้างฐานข้อมูล Firestore ในส่วน "Cloud Firestore" ให้คลิก "สร้างฐานข้อมูล"
หลังจากนั้น ให้สร้างฐานข้อมูลในโหมดทดสอบโดยทำดังนี้
สุดท้าย ให้เลือกภูมิภาค
ตอนนี้สิ่งเดียวที่เหลืออยู่คือการเพิ่มการกำหนดค่า Firebase ลงในสภาพแวดล้อม คุณดูการกำหนดค่าโปรเจ็กต์ได้ในคอนโซล Firebase
- คลิกไอคอนรูปเฟืองข้างภาพรวมโปรเจ็กต์
- เลือกการตั้งค่าโปรเจ็กต์
ในส่วน "แอปของคุณ" ให้เลือก "เว็บแอป" ดังนี้
จากนั้นลงทะเบียนแอปพลิเคชันและตรวจสอบว่าคุณได้เปิดใช้ "Firebase Hosting" แล้ว
หลังจากคลิก "ลงทะเบียนแอป" แล้ว คุณจะคัดลอกการกำหนดค่าลงใน src/environments/environment.ts
ได้โดยทำดังนี้
เมื่อเสร็จแล้ว ไฟล์การกำหนดค่าควรมีลักษณะดังนี้
src/environments/environment.ts
export const environment = {
production: false,
firebase: {
apiKey: '<your-key>',
authDomain: '<your-project-authdomain>',
databaseURL: '<your-database-URL>',
projectId: '<your-project-id>',
storageBucket: '<your-storage-bucket>',
messagingSenderId: '<your-messaging-sender-id>'
}
};
11. การย้ายข้อมูลไปยัง Firestore
ตอนนี้เราได้ตั้งค่า Firebase SDK แล้ว มาใช้ @angular/fire
เพื่อย้ายข้อมูลไปยัง Firestore กัน ก่อนอื่น มานำเข้าโมดูลที่เราต้องการใน AppModule
กัน
src/app/app.module.ts
...
import { environment } from 'src/environments/environment';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
@NgModule({
declarations: [AppComponent, TaskDialogComponent, TaskComponent],
imports: [
...
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
เนื่องจากเราจะใช้ Firestore จึงต้องแทรก AngularFirestore
ในเครื่องมือสร้างของ AppComponent
src/app/app.component.ts
...
import { AngularFirestore } from '@angular/fire/firestore';
@Component({...})
export class AppComponent {
...
constructor(private dialog: MatDialog, private store: AngularFirestore) {}
...
}
จากนั้น เราจะอัปเดตวิธีเริ่มต้นอาร์เรย์เลนว่ายน้ำดังนี้
src/app/app.component.ts
...
@Component({...})
export class AppComponent {
todo = this.store.collection('todo').valueChanges({ idField: 'id' }) as Observable<Task[]>;
inProgress = this.store.collection('inProgress').valueChanges({ idField: 'id' }) as Observable<Task[]>;
done = this.store.collection('done').valueChanges({ idField: 'id' }) as Observable<Task[]>;
...
}
ในที่นี้ เราใช้ AngularFirestore
เพื่อรับเนื้อหาของคอลเล็กชันจากฐานข้อมูลโดยตรง โปรดสังเกตว่า valueChanges
จะแสดงผล observable แทนอาร์เรย์ และเรายังระบุด้วยว่าฟิลด์รหัสสำหรับเอกสารในคอลเล็กชันนี้ควรมีชื่อว่า id
เพื่อให้ตรงกับชื่อที่เราใช้ในอินเทอร์เฟซ Task
Observable ที่ valueChanges
แสดงผลจะปล่อยคอลเล็กชันของงานทุกครั้งที่มีการเปลี่ยนแปลง
เนื่องจากเราทำงานกับ Observable แทนอาร์เรย์ เราจึงต้องอัปเดตวิธีเพิ่ม นำออก และแก้ไขงาน รวมถึงฟังก์ชันการย้ายงานระหว่างเลน เราจะใช้ Firebase SDK เพื่ออัปเดตข้อมูลในฐานข้อมูลแทนการเปลี่ยนแปลงอาร์เรย์ในหน่วยความจำ
ก่อนอื่นมาดูว่าการเรียงลำดับใหม่จะมีลักษณะอย่างไร แทนที่เมธอด drop
ใน src/app/app.component.ts
ด้วย
src/app/app.component.ts
drop(event: CdkDragDrop<Task[]>): void {
if (event.previousContainer === event.container) {
return;
}
const item = event.previousContainer.data[event.previousIndex];
this.store.firestore.runTransaction(() => {
const promise = Promise.all([
this.store.collection(event.previousContainer.id).doc(item.id).delete(),
this.store.collection(event.container.id).add(item),
]);
return promise;
});
transferArrayItem(
event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex
);
}
ในข้อมูลโค้ดด้านบน โค้ดใหม่จะได้รับการไฮไลต์ หากต้องการย้ายงานจากเลนว่ายน้ำปัจจุบันไปยังเลนเป้าหมาย เราจะนำงานออกจากคอลเล็กชันแรกและเพิ่มลงในคอลเล็กชันที่ 2 เนื่องจากเราดำเนินการ 2 อย่างที่ต้องการให้ดูเหมือนเป็นอย่างเดียว (เช่น ทำให้การดำเนินการเป็นแบบอะตอม) เราจึงเรียกใช้การดำเนินการเหล่านั้นในธุรกรรม Firestore
ต่อไป เราจะอัปเดตเมธอด editTask
เพื่อใช้ Firestore กัน ภายในตัวแฮนเดิลกล่องโต้ตอบปิด เราต้องเปลี่ยนโค้ด 2 บรรทัดต่อไปนี้
src/app/app.component.ts
...
dialogRef.afterClosed().subscribe((result: TaskDialogResult|undefined) => {
if (!result) {
return;
}
if (result.delete) {
this.store.collection(list).doc(task.id).delete();
} else {
this.store.collection(list).doc(task.id).update(task);
}
});
...
เราเข้าถึงเอกสารเป้าหมายที่สอดคล้องกับงานที่เราจัดการโดยใช้ Firestore SDK แล้วลบหรืออัปเดตเอกสารนั้น
สุดท้าย เราต้องอัปเดตวิธีการสร้างงานใหม่ แทนที่ this.todo.push('task')
ด้วย this.store.collection('todo').add(result.task)
โปรดสังเกตว่าตอนนี้คอลเล็กชันของเราไม่ใช่อาร์เรย์ แต่เป็น Observable เราต้องอัปเดตเทมเพลตของ AppComponent
เพื่อให้แสดงภาพได้ เพียงแทนที่การเข้าถึงพร็อพเพอร์ตี้ todo
, inProgress
และ done
ทั้งหมดด้วย todo | async
, inProgress | async
และ done | async
ตามลำดับ
ไปป์แบบไม่พร้อมกันจะติดตาม Observable ที่เชื่อมโยงกับคอลเล็กชันโดยอัตโนมัติ เมื่อ Observable ปล่อยค่าใหม่ Angular จะเรียกใช้การตรวจหาการเปลี่ยนแปลงและประมวลผลอาร์เรย์ที่ปล่อยออกมาโดยอัตโนมัติ
ตัวอย่างเช่น มาดูการเปลี่ยนแปลงที่เราต้องทำในtodo
เลนว่ายน้ำกัน
src/app/app.component.html
<mat-card
cdkDropList
id="todo"
#todoList="cdkDropList"
[cdkDropListData]="todo | async"
[cdkDropListConnectedTo]="[doneList, inProgressList]"
(cdkDropListDropped)="drop($event)"
class="list">
<p class="empty-label" *ngIf="(todo | async)?.length === 0">Empty list</p>
<app-task (edit)="editTask('todo', $event)" *ngFor="let task of todo | async" cdkDrag [task]="task"></app-task>
</mat-card>
เมื่อส่งข้อมูลไปยังคำสั่ง cdkDropList
เราจะใช้ไปป์แบบไม่พร้อมกัน ซึ่งจะเหมือนกับภายในคำสั่ง *ngIf
แต่โปรดทราบว่าเรายังใช้การเชื่อมโยงแบบออปชัน (หรือที่เรียกว่าตัวดำเนินการการนำทางที่ปลอดภัยใน Angular) เมื่อเข้าถึงพร็อพเพอร์ตี้ length
เพื่อให้แน่ใจว่าเราจะไม่ได้รับข้อผิดพลาดรันไทม์หาก todo | async
ไม่ใช่ null
หรือ undefined
ตอนนี้เมื่อสร้างงานใหม่ในอินเทอร์เฟซผู้ใช้และเปิด Firestore คุณควรเห็นสิ่งที่คล้ายกับนี้
12. การปรับปรุงการอัปเดตแบบมองโลกในแง่ดี
ในแอปพลิเคชัน เรากำลังดำเนินการอัปเดตแบบมองโลกในแง่ดี เรามีแหล่งข้อมูลที่เชื่อถือได้ใน Firestore แต่ในขณะเดียวกันก็มีสำเนาของงานในเครื่องด้วย เมื่อใดก็ตามที่ Observable ใดๆ ที่เชื่อมโยงกับคอลเล็กชันปล่อยข้อมูลออกมา เราจะได้รับอาร์เรย์ของงาน เมื่อการกระทําของผู้ใช้เปลี่ยนสถานะ เราจะอัปเดตค่าในเครื่องก่อน แล้วจึงเผยแพร่การเปลี่ยนแปลงไปยัง Firestore
เมื่อย้ายงานจากเลนหนึ่งไปยังอีกเลนหนึ่ง เราจะเรียกใช้ transferArrayItem,
ซึ่งทำงานบนอินสแตนซ์อาร์เรย์ในเครื่องที่แสดงถึงงานในแต่ละเลน Firebase SDK จะถือว่าอาร์เรย์เหล่านี้เป็นแบบแก้ไขไม่ได้ ซึ่งหมายความว่าในครั้งถัดไปที่ Angular เรียกใช้การตรวจหาการเปลี่ยนแปลง เราจะได้รับอินสแตนซ์ใหม่ของอาร์เรย์ดังกล่าว ซึ่งจะแสดงสถานะก่อนหน้าก่อนที่เราจะโอนงาน
ในขณะเดียวกัน เราจะทริกเกอร์การอัปเดต Firestore และ Firebase SDK จะทริกเกอร์การอัปเดตด้วยค่าที่ถูกต้อง ดังนั้นในเวลาไม่กี่มิลลิวินาที อินเทอร์เฟซผู้ใช้จะเข้าสู่สถานะที่ถูกต้อง ซึ่งจะทำให้งานที่เราเพิ่งโอนย้ายกระโดดจากรายการแรกไปยังรายการถัดไป คุณจะเห็นการทำงานนี้ได้ชัดเจนใน GIF ด้านล่าง
วิธีที่ถูกต้องในการแก้ปัญหานี้จะแตกต่างกันไปในแต่ละแอปพลิเคชัน แต่ในทุกกรณี เราต้องตรวจสอบว่าเรายังคงรักษาสถานะที่สอดคล้องกันไว้จนกว่าข้อมูลจะอัปเดต
เราสามารถใช้ประโยชน์จาก BehaviorSubject
ซึ่งครอบคลุม Observer เดิมที่เราได้รับจาก valueChanges
BehaviorSubject
จะเก็บอาร์เรย์ที่เปลี่ยนแปลงได้ซึ่งคงการอัปเดตจาก transferArrayItem
ไว้
หากต้องการแก้ไข เราเพียงแค่อัปเดต AppComponent
โดยทำดังนี้
src/app/app.component.ts
...
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { BehaviorSubject } from 'rxjs';
const getObservable = (collection: AngularFirestoreCollection<Task>) => {
const subject = new BehaviorSubject<Task[]>([]);
collection.valueChanges({ idField: 'id' }).subscribe((val: Task[]) => {
subject.next(val);
});
return subject;
};
@Component(...)
export class AppComponent {
todo = getObservable(this.store.collection('todo')) as Observable<Task[]>;
inProgress = getObservable(this.store.collection('inProgress')) as Observable<Task[]>;
done = getObservable(this.store.collection('done')) as Observable<Task[]>;
...
}
สิ่งที่เราทำในข้อมูลโค้ดด้านบนคือการสร้าง BehaviorSubject
ซึ่งจะปล่อยค่าทุกครั้งที่ Observable ที่เชื่อมโยงกับคอลเล็กชันมีการเปลี่ยนแปลง
ทุกอย่างทำงานได้ตามที่คาดไว้ เนื่องจาก BehaviorSubject
จะใช้อาร์เรย์ซ้ำในการเรียกใช้การตรวจหาการเปลี่ยนแปลง และจะอัปเดตก็ต่อเมื่อเราได้รับค่าใหม่จาก Firestore เท่านั้น
13. การทำให้แอปพลิเคชันใช้งานได้
สิ่งที่เราต้องทำเพื่อทำให้แอปใช้งานได้คือเรียกใช้คำสั่งต่อไปนี้
ng deploy
คำสั่งนี้จะทำสิ่งต่อไปนี้
- สร้างแอปด้วยการกำหนดค่าเวอร์ชันที่ใช้งานจริงโดยใช้การเพิ่มประสิทธิภาพขณะคอมไพล์
- ทำให้แอปใช้งานได้กับโฮสติ้งของ Firebase
- แสดงผล URL เพื่อให้คุณดูตัวอย่างผลลัพธ์ได้
14. ขอแสดงความยินดี
ขอแสดงความยินดี คุณสร้างกระดานคัมบังด้วย Angular และ Firebase ได้สำเร็จแล้ว
คุณสร้างอินเทอร์เฟซผู้ใช้ที่มี 3 คอลัมน์ซึ่งแสดงสถานะของงานต่างๆ คุณใช้ Angular CDK เพื่อติดตั้งใช้งานการลากและวางงานในคอลัมน์ต่างๆ จากนั้นคุณได้สร้างแบบฟอร์มสำหรับสร้างงานใหม่และแก้ไขงานที่มีอยู่โดยใช้ Angular Material จากนั้นคุณได้เรียนรู้วิธีใช้ @angular/fire
และย้ายสถานะแอปพลิเคชันทั้งหมดไปยัง Firestore สุดท้ายนี้ คุณได้ทําให้แอปพลิเคชันใช้งานได้กับโฮสติ้งของ Firebase แล้ว
สิ่งต่อไปที่ควรทำ
โปรดทราบว่าเราได้ติดตั้งใช้งานแอปพลิเคชันโดยใช้การกำหนดค่าทดสอบ ก่อนที่จะทําให้แอปใช้งานจริงได้ โปรดตรวจสอบว่าคุณได้ตั้งค่าสิทธิ์ที่ถูกต้องแล้ว ดูวิธีได้ที่นี่
ปัจจุบันเราไม่ได้เก็บลำดับของงานแต่ละรายการในสวิมเลนที่เฉพาะเจาะจง หากต้องการใช้ฟีเจอร์นี้ คุณสามารถใช้ฟิลด์ลำดับในเอกสารงานและจัดเรียงตามฟิลด์ดังกล่าวได้
นอกจากนี้ เรายังสร้างกระดานคัมบังสำหรับผู้ใช้เพียงคนเดียว ซึ่งหมายความว่าเรามีกระดานคัมบังเดียวสำหรับทุกคนที่เปิดแอป หากต้องการใช้กระดานแยกต่างหากสำหรับผู้ใช้แอปที่แตกต่างกัน คุณจะต้องเปลี่ยนโครงสร้างฐานข้อมูล ดูแนวทางปฏิบัติแนะนำของ Firestore ที่นี่