Angular X REST Api
Angular X REST Api
Overview
สร้าง CRUD ง่าย ๆ
เนื้อหา
Prerequisite
- Node JS
- Angular 13+
- Angular Bootstrap 5+
How to
- ติดตั้ง Node JS
-
ติดตั้ง Angular cli
npm install -g @angular/cli
-
สร้าง Angular web application ใหม่
ng new [name]
-
ลง package angular bootstrap
ng add @ng-bootstrap/ng-bootstrap
-
เพิ่ม Import icon ลงใน styles.css
@import "~bootstrap-icons/font/bootstrap-icons.css";
- ตั้งค่า base api url ที่ต้องการจะไปดึงข้อมูล
- environments/environment.ts
- web api sample jsonplaceholder
export const environment = { production: false, mockApiUrl: "https://jsonplaceholder.typicode.com", };
-
เพิ่ม modules ที่จะใช้งาน (app.module.ts)
import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { HttpClientModule } from "@angular/common/http"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, AppRoutingModule, NgbModule, HttpClientModule, FormsModule, ReactiveFormsModule, ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
- สร้าง Model สำหรับกำหนด Type ในการรับ ส่งข้อมูล
- สร้างโฟลเดอร์ชื่อ models
- สร้างไฟล์ใหม่ชื่อ post-blog.model.ts
export interface PostBlogModel { userId: number; id: number; title: string; body: string; }
-
สร้าง Layout หน้าเว็บ (app.component.html) การทำงานของหน้านี้จะแบ่งเป็น 2 คอลัมน์ โดย คอลัมน์แรกจะเป็นหน้ารายการ และคอลัมน์ที่ 2 จะเป็นส่วนของการ maintenance
<div class="container-fluid h-100 w-100"> <div class="row p-4"> <div class="col-12 col-md-6"> <h2>Posts</h2> <!-- Search --> <div class="input-group"> <div class="input-group-prepend"> <div class="input-group-text"> <i class="bi bi-search"></i> </div> </div> <input type="text" class="form-control" autocomplete="off" id="search" name="search" [(ngModel)]="term" (ngModelChange)="onSearch()" /> </div> <!-- Table --> <div *ngIf="isPostsLoading; else showPosts" class="text-center"> <span class="spinner-border text-primary"></span> </div> <ng-template #showPosts> <div style="height: 600px" class="overflow-auto my-4"> <table class="table table-bordered table-hover"> <thead> <tr> <th>UserId</th> <th>Id</th> <th>Title</th> <th>Action</th> </tr> </thead> <tbody> <tr *ngFor="let post of filteredPosts"> <td></td> <td></td> <td></td> <td> <button class="btn btn-sm btn-outline-primary" (click)="onSelectClick(post.id)" > Select </button> </td> </tr> </tbody> </table> </div> </ng-template> </div> <div class="col-12 col-md-6"> <h2>Maintenance</h2> <form [formGroup]="form"> <div class="form-grop"> <label for="title">Title</label> <input type="text" id="title" name="title" formControlName="title" class="form-control" /> </div> <div class="form-group"> <label for="body">Body</label> <textarea name="body" id="body" formControlName="body" cols="30" rows="10" class="form-control" ></textarea> </div> <div *ngIf="isMaintenanceLoading" class="text-center"> <span class="spinner spinner-border text-primary"></span> </div> <div *ngIf="!isEdit" class="text-center"> <button class="btn btn-outline-primary" type="button" (click)="onAdd()" [disabled]="!form.valid || isMaintenanceLoading" > Add </button> </div> <div *ngIf="isEdit" class="text-center"> <button type="button" class="btn btn-outline-secondary" [disabled]="isMaintenanceLoading" (click)="onCancel()" > Cancel </button> <button type="button" class="btn btn-outline-warning mx-2" (click)="onEdit()" [disabled]="!form.valid || isMaintenanceLoading" > Edit </button> <button type="button" class="btn btn-outline-danger" (click)="onDelete()" [disabled]="!form.valid || isMaintenanceLoading" > Delete </button> </div> </form> </div> </div> </div>
สร้าง Service รองรับการทำงานกับ Api
-
สร้างไฟล์ post-service ด้วยคำสั่งช่วยสร้าง
ng g s services/post --skip-tests
-
สร้างฟังก์สำหรับการทำงานกับ api ต่าง ๆ ประกาศ constructor และ life cycle function บางตัว
@Injectable({ providedIn: "root", }) export class PostService implements OnInit, OnDestroy { constructor(private http: HttpClient) {} ngOnInit(): void {} ngOnDestroy(): void {} }
-
สร้างตัวแปรเพื่อรองรับการทำงานเป็นตัวพักรายการ post
posts: Subject<PostBlogModel[]>; constructor(private http: HttpClient) { this.posts = new Subject<PostBlogModel[]>(); } ngOnInit(): void { this.posts.next([]); } ngOnDestroy(): void { this.posts.unsubscribe(); }
-
สร้างฟังกชันก์สำหรับดึงรายการ post
// get all posts getAllPost(): Observable<PostBlogModel[]> { this.http.get(`${environment.mockApiUrl}/posts`).subscribe({ next: (result: any) => { this.posts.next(result); }, }); return this.posts.asObservable(); }
-
สร้างฟังก์ชันสำหรับการเพิ่ม post ใหม่
// create new post createPost(newPost: PostBlogModel) { return this.http.post(`${environment.mockApiUrl}/posts`, newPost); }
-
สร้างฟังก์ชันสำหรับการอัปเดต post
// update post updatePost(newPost: PostBlogModel) { return this.http.put( `${environment.mockApiUrl}/posts/${newPost.id}`, newPost ); }
-
สร้างฟังก์ชันสำหรับการลบ post
// delete post deletePost(id: number) { return this.http.delete(`${environment.mockApiUrl}/posts/${id}`); }
จัดการการทำงานหลังบ้านของไฟล์ Layout html (app.component.html)
-
เพิ่ม implements onInit
@Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"], }) export class AppComponent implements OnInit { ngOnInit(): void {} }
-
ประกาศตัวแปร
// ใช้เป็น temporary ตอนดึงข้อมูลครั้งแรก posts: PostBlogModel[] = []; // ใช้สำหรับการฟิลเตอร์ตอนค้นหา filteredPosts: PostBlogModel[]; // ใช้เป็น temporary สำหรับ post ทำเลือกอยู่ ณ เวลานั้น selectedPost!: PostBlogModel; // Boolean ใช้ประกอบการแสดงผลบนเว็บ isPostsLoading: boolean; isMaintenanceLoading: boolean; isEdit: boolean; term: string; form: FormGroup = new FormGroup({ userId: new FormControl(0), id: new FormControl(0), title: new FormControl('', Validators.required), body: new FormControl('', Validators.required), }); constructor(private postService: PostService) { this.isPostsLoading = false; this.isEdit = false; this.isMaintenanceLoading = false; this.term = ''; this.filteredPosts = []; }
-
เริ่มต้นการดึงข้อมูล Post เมื่อมายังหน้าแรก
ngOnInit(): void { // กำกับให้เปิด spinner this.isPostsLoading = true; // ดึงข้อมูลจาก api this.postService.getAllPost().subscribe({ next: (result) => { // ถ้า response ok จะเข้า blog นี้ // เก็บรายการที่ดึงได้ทั้งหมด this.posts = result; // ค้นหาตามฟิลเตอร์ this.onSearch(); // mark spinner ปิด this.isPostsLoading = false; }, error: (err) => { // ถ้า response error จะเข้า blog นี้ this.isPostsLoading = false; }, }); }
-
สร้างฟังก์ชันค้นหา
onSearch() { // ถ้า term blank ให้ return post ทั้งหมด if (this.term.length == 0) { this.filteredPosts = this.posts; return; } this.filteredPosts = this.posts.filter( (item) => item.title.indexOf(this.term.trim()) > -1 ); }
-
สร้างฟังก์ชันแสดงผลเมื่อเลือกรายการบนตาราง
onSelectClick(id: number) { // เก็บข้อมูล post ที่เลือกขณะนั้น this.selectedPost = this.filteredPosts.find((item) => item.id == id)!; // ถ้าไม่เจอ ไม่ให้ทำงานต่อ if (this.selectedPost == null) { return; } // อัปเดตค่าลงไปในฟอร์ม this.form.patchValue({ ...this.selectedPost, }); // กำกับสถานะของหน้า ณ ตอนนั้นว่ากำลังอยู่ในช่วง maintenance this.isEdit = true; }
-
สร้างฟังก์ชันสำหรับการเพิ่มรายการ
onAdd() { let newPost: PostBlogModel; let latestId: number = this.posts.length + 1; this.isMaintenanceLoading = true; // เตรียมข้อมูล post ใหม่ที่กำลังจะเพิ่มเข้าไป newPost = { ...this.form.value }; newPost.id = latestId; newPost.userId = 1; // เรียก api เพื่อสร้าง post ใหม่ this.postService.createPost(newPost).subscribe({ next: (res) => { // ถ้าสร้างสำเร็จ ให้เพิ่มข้อมูลลงไปใน temporary และประมวลผลหน้าเว็บใหม่ this.posts = [newPost, ...this.posts]; this.onSearch(); this.form.reset(); this.isMaintenanceLoading = false; }, error: (err) => { this.isMaintenanceLoading = false; }, }); }
-
สร้างฟังก์ชันสำหรับการแก้ไข post
onEdit() { let newPost: PostBlogModel; newPost = { ...this.form.value }; this.isMaintenanceLoading = true; this.postService.updatePost(newPost).subscribe({ next: (res) => { this.posts.forEach((post, i) => { if (post.id == newPost.id) { this.posts[i] = newPost; this.selectedPost = newPost; } }); this.onSearch(); this.isMaintenanceLoading = false; }, error: (err) => { console.error(err); this.isMaintenanceLoading = false; }, }); }
-
สร้างฟังก์ชันสำหรับการลบ post
onDelete() { let tempPosts: PostBlogModel[] = []; this.isMaintenanceLoading = true; this.postService.deletePost(this.selectedPost.id).subscribe({ next: (res) => { this.posts.forEach((post) => { if (post.id != this.selectedPost.id) { tempPosts.push(post); } }); this.posts = tempPosts.slice(); this.onSearch(); this.form.reset(); this.isEdit = false; this.isMaintenanceLoading = false; }, error: (err) => { console.error(err); this.isMaintenanceLoading = false; }, }); }
-
สร้างฟังก์ชันสำหรับการยกเลิก maintenance
onCancel() { this.form.reset(); this.isEdit = false; }
Extension Mock api endpints
- สร้าง Model
- สร้างโฟลเดอร์ Models
- สร้างไฟล์ชื่อ PurchaseItemMaster_01.cs
namespace api_test.Models { public class PurchaseItemMaster_01 { public string Div { get; set; } public string ItemNo { get; set; } public string ItemName { get; set; } public double OnHand { get; set; } } }
-
สร้าง Mock api endpoints
[HttpPost] public IActionResult CreateMaster([FromBody] PurchaseItemMaster_01 body) { try { return StatusCode((int)HttpStatusCode.Created); } catch (Exception ex) { return StatusCode((int)HttpStatusCode.InternalServerError, new { message = ex.Message }); } } [HttpPut("{plac}/{divi}/{opec}/{itno}")] public IActionResult UpdateMaster(string place, string divi, string opec, [FromBody] PurchaseItemMaster_01 body) { try { return StatusCode((int)HttpStatusCode.OK); } catch (Exception ex) { return StatusCode((int)HttpStatusCode.InternalServerError, new { message = ex.Message }); } } [HttpDelete("{plac}/{divi}/{opec}/{itno}")] public IActionResult DeleteMaster(string plac, string divi, string opec, string itno) { try { return StatusCode((int)HttpStatusCode.NoContent); } catch (Exception ex) { return StatusCode((int)HttpStatusCode.InternalServerError, new { message = ex.Message }); } }