Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การใช้ Angular กับ Rails 5

คุณเคยได้ยินเรื่องราวมาก่อน คุณมีแอปพลิเคชันที่ทำงานบน API แบ็คเอนด์ที่กระจายอำนาจและทำงานได้อย่างสมบูรณ์ และฟรอนต์เอนด์ที่สร้างด้วยชุดเครื่องมือทั่วไป

ตอนนี้คุณต้องการย้ายไปที่ Angular หรือบางทีคุณแค่กำลังมองหาวิธีผสานรวม Angular กับโปรเจ็กต์ Rails ของคุณ เพราะคุณชอบสิ่งนี้มากกว่า เราไม่โทษคุณ

ด้วยวิธีการดังกล่าว คุณสามารถใช้ประโยชน์จากทั้งสองโลกและตัดสินใจว่าคุณต้องการใช้คุณลักษณะจาก Rails หรือ Angular เพื่อจัดรูปแบบสิ่งต่างๆ เป็นต้น

สิ่งที่เราจะสร้าง

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

ในตอนท้ายของบทความ คุณจะได้เรียนรู้แนวคิดพื้นฐานเกี่ยวกับ Angular และวิธีตั้งค่าโครงการส่วนหลังของ Rails ที่ผสานรวมกับ Angular สำหรับ front-end โดยตรงดังที่แสดงด้านล่าง:

การใช้ Angular กับ Rails 5 CRUD ของผู้ใช้ที่สร้างด้วย Rails และ Angular

แอปพลิเคชันจะจัดการการดำเนินการ CRUD ทั้งสี่บนโดเมนของผู้ใช้ที่ดึงมาจากบริการเว็บทดสอบปลอมภายนอก แอปจะถูกสร้างขึ้นบนสถาปัตยกรรม MVC โดยแต่ละเลเยอร์ของ Angular จะอธิบายโดยละเอียดเพื่อช่วยให้คุณเข้าใจได้ดีขึ้นว่าสิ่งต่าง ๆ เชื่อมโยงกันอย่างไร สไตล์ขึ้นอยู่กับ Bootstrap

ตั้งค่า

อย่างที่คุณอาจเดาได้ เราจำเป็นต้องใช้ซอฟต์แวร์ต่อไปนี้:

  • Ruby (ฉันเลือกเวอร์ชัน 2.7.0preview1),
  • Ruby and Rails (ฉันใช้เวอร์ชัน 5.0.7.2)
  • Node.js (ฉันกำลังใช้ v13.7.0)
  • เส้นด้าย (อย่างน้อยเวอร์ชัน 1.22.4)

ตรวจสอบให้แน่ใจว่าได้ติดตั้งทุกอย่างอย่างถูกต้อง จากนั้นเราก็ไปต่อกันที่โครงการ เลือกโฟลเดอร์ที่คุณต้องการและเรียกใช้คำสั่งต่อไปนี้:

rails new crud-rails-angular

รอให้การตั้งค่าเสร็จสิ้นและเปิดโครงการภายใน IDE ที่คุณชื่นชอบ เราจะทำงานกับ VS Code สำหรับบทความนี้เพราะว่าง่าย มีประสิทธิภาพ และรวมทั้งไวยากรณ์ Rails และ Angular อย่างราบรื่น

หากคุณใช้ Rails 5 มาระยะหนึ่งแล้ว คุณอาจสังเกตเห็นว่ามันเป็นnew คำสั่งสร้างจุดบกพร่องภายใน Gemfile สำหรับการกำหนดค่า SQLite มันมาโดยไม่มีเวอร์ชันขั้นต่ำ และนั่นจะทำให้มันทำงานโดยมีข้อผิดพลาด มาแก้ไขปัญหานี้โดยอัปเดตเป็น

gem 'sqlite3', '~> 1.3.10'

สมบูรณ์แบบ!

การตั้งค่าเว็บแพ็คเกอร์

วิธีที่ดีที่สุดในการจัดการแอปพลิเคชันที่เหมือน JavaScript ใน Rails คือการใช้ Webpacker โดยใช้ประโยชน์จาก Webpack เบื้องหลังเพื่อมอบคุณลักษณะต่างๆ เช่น การประมวลผลล่วงหน้าและการรวมแอป JavaScript เช่น Angular ลงในแอปพลิเคชัน Rails ที่มีอยู่

ในการติดตั้ง เพียงเพิ่มบรรทัดใหม่ใน Gemfile . ของคุณ :

gem 'webpacker', '~> 4.3.x'

ซึ่งจะทำให้มั่นใจได้ว่าคุณจะติดตั้งเวอร์ชันล่าสุด ถัดไป ให้รันคำสั่งต่อไปนี้:

bundle install
bundle exec rake webpacker:install
bundle exec rake webpacker:install:angular

คำสั่งแรกจะดาวน์โหลดและอัปเดตการพึ่งพา Rails ที่เพิ่มเข้ามา

อันที่สองเทียบเท่ากับ npm install เนื่องจากมันสร้าง node_modules และติดตั้งการขึ้นต่อกันของ Angular ที่จำเป็น เช่น Babel, Sass, Browserlist และ Webpack ตอนนี้ เรามีทั้งแอป Node และ Rails ในโครงการเดียวกัน

ในคำสั่งล่าสุด เรามี npm install angular ซึ่งจะดาวน์โหลดการพึ่งพาที่ต้องใช้ Angular ทั้งหมดและทำให้ทำงานควบคู่ไปกับโปรเจ็กต์ Rails ของเรา

ที่ส่วนท้ายของคำสั่งเหล่านี้ คุณยังสามารถดู package.json สร้างไฟล์แล้ว การอ้างอิงที่จำเป็นทั้งหมดของเราจะถูกวางไว้ที่นั่น และคุณสามารถเพิ่มสิ่งที่คุณต้องการได้ในอนาคต

นอกจากนี้ บางโฟลเดอร์และไฟล์ยังถูกสร้างขึ้นภายใต้ /app โฟลเดอร์ เช่น /javascript . ใหม่ . ในโฟลเดอร์นี้ คุณมี /hello_angular . อยู่แล้ว โฟลเดอร์ที่สร้างขึ้นเพื่อรองรับการเริ่มต้นการพัฒนาของคุณ

เพื่อให้ได้เวลา ฉันจะขอให้คุณทำมิเรอร์โฟลเดอร์และโครงสร้างไฟล์ของคุณด้วยสิ่งต่อไปนี้:

การใช้ Angular กับ Rails 5

การปรับเชิงมุมบางส่วน

Webpacker แนะนำชุดการปรับปรุงภายในโครงการ Rails ที่คุณสร้างขึ้น เลยใช้เวลาจัดระเบียบบ้านกันสักหน่อย

ขั้นแรก เปิด application.js . ของคุณ ไฟล์อยู่ใต้ /packs โฟลเดอร์ (ดังรูปด้านบน) และเพิ่มรหัสต่อไปนี้:

import "core-js/stable";
import "regenerator-runtime/runtime";

การนำเข้าเหล่านี้ทำงานเป็นกำลังเสริมเพื่อทำให้สภาพแวดล้อม JavaScript ภายในโปรเจ็กต์ Rails มีความเสถียร

ตอนนี้ เราต้องแจ้ง Rails ว่าต้องเลือกเอาท์พุตไปยังหน้าใด เมื่อ Webpacker แพ็คของเสร็จแล้ว มันจะสร้างไฟล์สแตติกที่สามารถแจกจ่ายได้จำนวนมากที่ Rails ต้องระวัง

ไปที่ application.html.erb ไฟล์ภายใต้ app/views/layout โฟลเดอร์และเปลี่ยน <head> แท็กเนื้อหาต่อไปนี้:

<head>
  <title>CrudRailsAngular</title>
  <base href="/" />
  <!-- 1 -->
  <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous" />
  <!-- 2 -->
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application' %>
  <!-- 3 -->
</head>

มาแบ่งเรื่องนี้กันสักหน่อย:

  1. ที่นี่ เรากำลังเพิ่ม base ซึ่งบอก Rails ว่าจะต้องดูที่ไหนเมื่อแอปพลิเคชันเริ่มทำงาน
  2. เราจะใช้ Bootstrap เพื่อสรุปรูปแบบให้กับเพจ เพื่อให้เราสามารถมุ่งเน้นที่การใช้งานเท่านั้น
  3. นี่คือตำแหน่งที่คุณต้องวางแท็ก Webpacker ที่แมปกับ /packs เนื้อหาของโฟลเดอร์ (ซึ่ง Webpacker จะสร้างโดยอัตโนมัติหลังการคอมไพล์ทุกครั้ง)

โมเดลและฐานข้อมูล

ไปที่การตั้งค่าฐานข้อมูล เพื่อให้สิ่งต่าง ๆ เร็วขึ้น เราจะสร้างโมเดลใหม่ที่ชื่อว่า User . นี่คือคำสั่งที่คุณต้องเรียกใช้เพื่อให้บรรลุ:

rails g scaffold User name:string age:integer address:text && rake db:migrate

มันจะสร้างโฟลเดอร์และไฟล์ของโมเดลทั้งหมดของเรา ซึ่งเราจะต้องทำให้ Rails จัดการข้อมูลฐานข้อมูล (จาก SQLite) และเก็บไว้ในการดำเนินการ CRUD ของเรา

แล้วคุณจะเห็นไฟล์ใหม่ XXX1_create_users.rb ถูกสร้างขึ้นภายใต้ db/migrate/ โฟลเดอร์ เปิดแล้วคุณจะเห็น CreateUsers . ที่สร้างขึ้นใหม่ บันทึก

ภายใน app/models/ โฟลเดอร์ คุณจะเห็น User . ที่สร้างขึ้นแล้ว โมเดลที่ user.rb ไฟล์.

ตอนนี้ เปิด db/seeds.rb ไฟล์และเพิ่มรหัสต่อไปนี้:

User.create(name: 'Luke Wan', age: 23, address: '123 Test St.')
User.create(name: 'Mary Poppins', age: 41, address: '123 ABC St.')
User.create(name: 'John Neilman', age: 76, address: '000 Test St.')

รหัสนี้จะเริ่มต้น Users . ของเรา ตารางที่มีข้อมูลบางส่วนเมื่อเริ่มต้น บันทึกและเรียกใช้คำสั่ง:

rake db:seed

สิ่งนี้จะทำให้ตารางผ่านคำสั่งที่แสดงด้านบน ถัดไป คุณสามารถเข้าสู่ฐานข้อมูล SQLite และตรวจสอบว่าใช้งานได้หรือไม่โดยใช้คำสั่ง:

sqlite3 db/development.sqlite3

จากนั้นเลือกข้อมูลตาราง:

select * from users;

และคุณอาจเห็นผล

ส่วนประกอบผู้ใช้

คุณจะต้องติดตั้งการพึ่งพาอีกสองสามรายการเพื่อช่วยในการแปลง HTML และ CSS เป็นหน้า Rails ของเรา เพิ่มเราเตอร์ Angular, form libs และ ngx-bootstrap ซึ่งเราจะใช้เพื่ออำนวยความสะดวกในการสร้างและจัดการส่วนประกอบ Bootstrap ดังนั้น ให้ออกคำสั่งต่อไปนี้:

yarn add @angular/router @angular/forms html-loader css-loader ngx-bootstrap

อย่างไรก็ตาม ก่อนที่เราจะข้ามไปยังโค้ดส่วนประกอบ มีแนวคิดที่สำคัญบางอย่างที่เราต้องชี้ให้เห็น โดยเริ่มจากกายวิภาคขององค์ประกอบเชิงมุม

ส่วนประกอบคืออะไร

ใน Angular มีส่วนประกอบเพื่อเชื่อมต่อมุมมองของคุณกับตรรกะของแอปพลิเคชันที่สร้างใน TypeScript

กล่าวอีกนัยหนึ่ง คอมโพเนนต์เป็นเหมือนคอนเทนเนอร์สำหรับตรรกะทั้งหมดที่มุมมองของคุณจำเป็นต้องสนับสนุนการทำงาน กำหนดค่าที่มุมมองจะแสดงและควบคุมโฟลว์ของพวกเขา มันเทียบเท่ากับ 'คอนโทรลเลอร์' ในเฟรมเวิร์กที่คล้ายคลึงกัน

ในการสร้างส่วนประกอบ สิ่งที่คุณต้องทำคือกำหนดคลาสใหม่ ใช้งาน OnInit อินเทอร์เฟซ และใส่คำอธิบายประกอบคลาสด้วย @Component มัณฑนากร:

export class UserIndexComponent implements OnInit {
    constructor() { ... }

    ngOnInit() { ... }
}

@Component และ OnInit

@Component มัณฑนากรมีความสำคัญเนื่องจากจะทำเครื่องหมายคลาสนี้เป็นองค์ประกอบเชิงมุมที่จดจำได้ และจัดเตรียมการกำหนดค่าข้อมูลเมตาที่ช่วยให้ Angular จัดการกับพวกมันเกี่ยวกับการประมวลผล การสร้างอินสแตนซ์ และการใช้งานระหว่างรันไทม์

ใช้การกำหนดค่าข้อมูลเมตาต่อไปนี้:

@Component({
    selector: "users",
    template: templateString,
})

ที่นี่ selector บอก Angular ว่าค่าที่ระบุคือตัวเลือก CSS ที่อาจใช้เพื่อระบุคำสั่งปัจจุบันในเทมเพลต ใช่ มันเป็นเทมเพลตเดียวกันกับคุณสมบัติข้อมูลเมตาถัดไป

OnInit อย่างไรก็ตาม อินเทอร์เฟซเป็นทางเลือก และเป็นวิธีที่ดีในการเริ่มต้นสิ่งต่าง ๆ ก่อนที่ส่วนประกอบจะสิ้นสุดวงจรชีวิต มันทำงานเหมือนวิธีการหลังการสร้าง

การพึ่งพาการฉีด

Angular เป็น DI (Dependency Injection ) เฟรมเวิร์ก ซึ่งเป็นคุณลักษณะที่เพิ่มโมดูลและประสิทธิภาพการทำงาน

การพึ่งพาใน Angular อาจแตกต่างกันไปจากบริการและพื้นที่เก็บข้อมูลของคุณ ไปจนถึงวัตถุธรรมดาประเภทใดก็ตามที่คุณรู้สึกว่าเหมาะสมที่จะถูกฉีดไปที่อื่นในโค้ด

หากต้องการเปลี่ยนคลาส "injectable" คุณจะต้องใส่คำอธิบายประกอบด้วย @Injectable มัณฑนากร:

@Injectable({
    providedIn: "root",
})
export class UserService {
    ...
}

providedIn ระบุว่าหัวฉีดใดจะให้หัวฉีดที่คุณกำลังสร้าง root ค่าบอก Angular ว่าหัวฉีดควรเป็นระดับแอปพลิเคชัน มีอีกมากมายที่คุณสามารถตรวจสอบได้ที่นี่

ในการใส่คลาสลงในส่วนประกอบ เช่น คุณขอให้ Angular ทำใน Constructor ของส่วนประกอบ:

constructor(
    private userService: UserService,
) {}

ง่ายนิดเดียว!

ส่วนประกอบสำเร็จรูป

ด้านล่างนี้ คุณจะพบรายการรหัสสุดท้ายสำหรับองค์ประกอบผู้ใช้ของเรา วางลงใน index.component.ts ภายใต้ javascript/hello_angular/app/ โฟลเดอร์

import { Component, OnInit, TemplateRef } from "@angular/core";
import { FormGroup, FormBuilder } from "@angular/forms";
import { BsModalRef, BsModalService } from "ngx-bootstrap/modal";

import templateString from "./index.component.html";
import { UserService } from "../user.service";
import { User } from "../user.class";

@Component({
  selector: "users",
  template: templateString,
})
export class UserIndexComponent implements OnInit {
  users: User[];
  modalRef: BsModalRef;
  userForm: FormGroup;
  isNew: Boolean;

  constructor(public fb: FormBuilder, private userService: UserService, private modalService: BsModalService) {}

  public newUser(template: TemplateRef<any>) {
    this.reset();
    this.modalRef = this.modalService.show(template);
  }

  public createUser() {
    this.userService.create(this.userForm.value).subscribe(() => {
      console.log("User created!");
      this.reset();

      this.modalRef.hide();
    });
  }

  public editUser(user, template: TemplateRef<any>) {
    this.isNew = false;
    this.userForm = this.fb.group({
      id: [user.id],
      name: [user.name],
      age: [user.age],
      address: [user.address],
    });

    this.modalRef = this.modalService.show(template);
  }

  public updateUser() {
    const { id } = this.userForm.value;
    this.userService.update(id, this.userForm.value).subscribe(() => {
      console.log("User updated!");
      this.reset();

      this.modalRef.hide();
    });
  }

  public deleteUser(id) {
    if (confirm("Are you sure?")) {
      this.userService.delete(id).subscribe(() => {
        console.log("User deleted!");
        this.reset();
      });
    }
  }

  ngOnInit() {
    this.reset();
  }

  public reset() {
    this.isNew = true;
    this.userService.getUsers().subscribe((users) => {
      this.users = users;
    });

    this.userForm = this.fb.group({
      id: [""],
      name: [""],
      age: [""],
      address: [""],
    });
  }
}

Users array จะเก็บข้อมูลตารางปัจจุบันที่แสดงบนหน้าจอและดึงมาจาก reset วิธีที่ในทางกลับกันเรียก Rails API ของเราผ่าน UserService (กำลังสร้าง)

userForm เป็นเพียงข้อมูลอ้างอิงเพื่อช่วยสร้างและอัปเดตผู้ใช้ของเรา เนื่องจากจะใช้แบบฟอร์มเดียวกันสำหรับการดำเนินการทั้งสองอย่าง isNew ช่วยในการระบุกระแสที่เราอยู่ในขณะนี้

ที่นี่ เรามีวิธีการเทียบเท่า CRUD สำหรับการดำเนินการแต่ละครั้ง แต่ละคนเรียก UserService . ตามลำดับ วิธีการส่งกระบวนการใน Rails API

นอกจากนี้เรายังต้องตั้งค่าโมดูล HTML เพื่อแปลงเทมเพลตของเราเป็น HTML (เราจะเห็นเพิ่มเติมเกี่ยวกับโมดูลในเร็วๆ นี้) ดังนั้น เปิด html.d.ts ไฟล์ภายในโฟลเดอร์เดียวกันและเพิ่ม:

declare module "*.html" {
  const content: string;
  export default content;
}

บริการและแบบจำลองเชิงมุม

ไปที่ UserService . ของ Angular กัน การสร้าง Angular เป็นเฟรมเวิร์ก เช่นเดียวกับ Rails ดังนั้นจึงหมายความว่าสามารถปฏิบัติตามกฎแม้ว่าจะหมายถึงการมีโมเดลที่ซ้ำกัน (หรือคล้ายกันมาก) เป็นต้น

โมเดลคืออะไร

โมเดลเชิงมุมเป็นออบเจ็กต์ง่ายๆ ที่เก็บแอตทริบิวต์ข้อมูลที่เหมาะสมร่วมกัน (กล่าวคือ เป็นส่วนประกอบสั้นๆ ของโดเมนของคุณ) เหมือนกับรุ่นอื่นๆ ในภาษาและเฟรมเวิร์กส่วนใหญ่

การรวมข้อมูลของคุณไว้ในที่เดียวช่วยได้มาก แทนที่จะทำซ้ำตลอดทั้งโค้ดเหมือนที่เราทำกับโมเดลผู้ใช้ของเรา:

export class User {
  constructor(public id: number, public name: string, public age: number, public address: string) {}
}

โปรดจำไว้ว่านี่คือ TypeScript ดังนั้นแอตทริบิวต์ของโมเดลของคุณต้องมีประเภทที่กำหนดไว้เสมอ

สร้างไฟล์ใหม่ชื่อ user.class.ts ภายใต้ javascript/hello_angular/app/user/ โฟลเดอร์แล้ววางโค้ดด้านบนลงไป

แล้วบริการล่ะ

บริการเป็นแนวคิดที่กว้าง แต่เราสามารถเข้าใจได้ว่าเป็นออบเจกต์ที่มีการกำหนดไว้อย่างดีและมีจุดประสงค์ ช่วยส่วนประกอบที่มีตรรกะที่ซับซ้อนมากขึ้น โดยให้บริการด้วยข้อมูลที่ประมวลผลและแปลงแล้ว ซึ่งมักจะมาจากบริการภายนอกหรือฐานข้อมูล

บริการไม่จำเป็นต้องมีคำอธิบายประกอบหรืออินเทอร์เฟซเฉพาะใดๆ คุณเพียงแค่สร้างคลาสและทำให้ ฉีดได้ อย่างที่เราเคยเห็นมาก่อน จากนั้นคุณสามารถฉีดเข้าไปในส่วนประกอบของคุณได้

บริการที่สังเกตได้

คุณสมบัติที่น่าสนใจอีกอย่างของ Angular คือช่วยให้คุณใช้ RxJS กับคลาสของคุณได้

ตัวอย่างเช่น ไคลเอ็นต์ HTTP เริ่มต้นของ Angular ซึ่งเป็นไคลเอ็นต์เดียวกับที่เราจะใช้เพื่อดึงข้อมูลจากบริการภายนอก ส่งคืน RxJS Observables . นี่คือเหตุผล เมื่อคุณเรียกใช้ UserService . ของเรา เมธอดภายในคอมโพเนนต์ผู้ใช้ คุณอาจ subscribe ไปที่ Observable ผลลัพธ์:

this.userService.getUsers().subscribe((users) => {
  this.users = users;
});

โปรดทราบว่าหากคุณไม่คุ้นเคยกับ RxJS เราขอแนะนำให้คุณอ่านเอกสารประกอบโดยย่อ ไม่ยากขนาดนั้น!;)

อีกครั้งใน javascript/hello_angular/app/user/ ให้สร้างไฟล์ใหม่ชื่อ user.service.ts . นี่คือเนื้อหา:

import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { map } from "rxjs/operators";
import { Observable } from "rxjs";

import { User } from "./user.class";

@Injectable({
  providedIn: "root",
})
export class UserService {
  constructor(private http: HttpClient) {}

  httpOptions = {
    headers: new HttpHeaders({
      "Content-Type": "application/json",
    }),
  };

  getUsers(): Observable<User[]> {
    return this.http.get("/users.json").pipe(
      map((users: User[]) =>
        users.map((user) => {
          return new User(user.id, user.name, user.age, user.address);
        })
      )
    );
  }

  create(user): Observable<User> {
    return this.http.post<User>("/users.json", JSON.stringify(user), this.httpOptions);
  }

  update(id, user): Observable<User> {
    return this.http.put<User>("/users/" + id + ".json", JSON.stringify(user), this.httpOptions);
  }

  delete(id) {
    return this.http.delete<User>("/users/" + id + ".json", this.httpOptions);
  }
}

คุณเห็นความคล้ายคลึงกันระหว่างองค์ประกอบนี้กับองค์ประกอบที่เราเพิ่งสร้างขึ้นหรือไม่? เนื่องจากเราต้องการการดำเนินการที่เกี่ยวข้องเพื่อสนับสนุนการดำเนินการในองค์ประกอบ

โปรดทราบว่า HttpClient ต้องฉีดเข้าไปในตัวสร้างของชั้นเรียนด้วย เราจึงสามารถใช้มันร่วมกับชั้นเรียนได้

การดำเนินการแต่ละครั้งจะเรียก HTTP ไปยัง Rails API ซึ่งเป็นการดำเนินการที่สร้างขึ้นโดยอัตโนมัติ

จำนวนการดู

Angular ทำงานร่วมกับเทมเพลตสำหรับมุมมอง เทมเพลตคือการผสมผสานระหว่าง HTML และ JavaScript แบบลำดับชั้นที่บอก Angular ถึงวิธีการแสดงแต่ละองค์ประกอบ

อย่างไรก็ตาม ก่อนที่จะดำเนินการสร้างมุมมองของเราต่อไป เรามาทำความเข้าใจว่า Angular แยกระบบเทมเพลตอย่างไร

คำสั่งเชิงมุม

เนื่องจากเทมเพลตเชิงมุมเป็นไดนามิกที่สำคัญ คำสั่ง . บางส่วน จำเป็นต้องขับเคลื่อน Angular ผ่านวิธีการเรนเดอร์ที่ถูกต้อง

คำสั่งเป็นเพียงคลาสที่มี @Directive มัณฑนากรเช่นส่วนประกอบ ใช่ @Component สืบทอดมาจาก @Directive ดังนั้นจึงเป็นคำสั่งอย่างเป็นทางการด้วย

อย่างไรก็ตาม ยังมีอีกสองประเภท:โครงสร้าง และ แอตทริบิวต์ คำสั่ง

คำสั่งโครงสร้าง

คำสั่งเหล่านี้แสดงถึงโครงสร้างแบบมีเงื่อนไขและแบบวนซ้ำที่แปลจาก JavaScript เป็นเทมเพลตเชิงมุม ช่วยทำให้เทมเพลตมีไดนามิกมากที่สุด เช่น คุณกำลังเขียนโปรแกรมภายในโค้ด JavaScript ของวานิลลา ยกตัวอย่างต่อไปนี้:

<tr *ngFor="let user of users">
  <td>{{ user.name }}</td>
</tr>

*ngFor directive บอกให้ Angular วนซ้ำอาร์เรย์ users และพิมพ์ชื่อผู้ใช้แต่ละรายไปที่ DOM

คำสั่งแอตทริบิวต์

สิ่งเหล่านี้ทำงานโดยตรงกับลักษณะที่ปรากฏหรือพฤติกรรมขององค์ประกอบ ยกตัวอย่างต่อไปนี้:

<form [formGroup]="userForm" (ngSubmit)="isNew ? createUser() : updateUser()" novalidate></form>

ที่นี่ เรากำลังแก้ไขลักษณะการทำงานของแบบฟอร์มโดยการตั้งค่า submit . แบบมีเงื่อนไข ฟังก์ชันและการใช้ประโยชน์จาก FormGroup . ของ Angular เพื่อผูกข้อมูลแต่ละอินพุตของแบบฟอร์ม

การผูกข้อมูล

การสร้างฟอร์มด้วยเฟรมเวิร์กของเว็บอาจเป็นงานที่ยุ่งยากและเกิดข้อผิดพลาดได้ หากไม่มีการเชื่อมโยงข้อมูล

Angular รองรับการเชื่อมโยงข้อมูลแบบสองทาง ซึ่งหมายความว่าคุณสามารถเชื่อมต่อส่วนต่างๆ ของเทมเพลตกับส่วนประกอบได้โดยตรง และในทางกลับกัน

แบบฟอร์มด้านบนเป็นตัวอย่างที่ดีของ FormGroup อำนาจผูกข้อมูล มันผูกแต่ละฟิลด์ของแบบฟอร์มกับ userForm . โดยอัตโนมัติ วัตถุที่สร้างขึ้นภายในองค์ประกอบของเรา

ใน editUser ตัวอย่างเช่น คุณสามารถดูการโยงเวอร์ชันตรงกันข้าม ซึ่ง userForm ค่าของถูกกำหนดไว้ภายในองค์ประกอบและจะสะท้อนถึงรูปแบบในมุมมอง

การสร้างมุมมองดัชนี

มาแยกย่อยเนื้อหาสำหรับ index.component.html ออกเป็นสองส่วน นี่คืออันแรก:

<div class="container pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
  <h1 class="display-4">User's Listing</h1>
  <p class="lead">A quick CRUD example of how to integrate Rails with Angular</p>

  <table class="table">
    <tr>
      <th>Id</th>
      <th>Name</th>
      <th>Age</th>
      <th>Address</th>
      <th>Actions</th>
    </tr>

    <tbody>
      <tr *ngFor="let user of users">
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>{{ user.age }}</td>
        <td>{{ user.address }}</td>
        <td colspan="2">
          <button class="btn btn-secondary" (click)="editUser(user, template)">Edit</button>
          |
          <button class="btn btn-danger" (click)="deleteUser(user.id)">Delete</button>
        </td>
      </tr>
    </tbody>
  </table>

  <button class="btn btn-primary float-right mt-4" (click)="newUser(template)">Insert New</button>
</div>

ส่วนใหญ่ประกอบด้วย HTML ธรรมดา เราจะไม่ลงรายละเอียดเกี่ยวกับคลาส Bootstrap

ส่วนสำคัญที่นี่คือ ngFor คำสั่งในแถวของตาราง มันช่วยวนซ้ำ Users array (จำได้ไหม?) พิมพ์แต่ละแอตทริบิวต์ไปยังเอาต์พุต HTML ผ่าน {{ … }} โอเปอเรเตอร์

เมื่อใดก็ตามที่คุณต้องการเพิ่มเหตุการณ์ DOM เช่น onClick เพียงใส่วงเล็บชื่อเหตุการณ์แล้วเพิ่มฟังก์ชันคอมโพเนนต์ที่จะเรียกเมื่อคลิก

การสร้างมุมมองโมดอล

ส่วนที่สองเกี่ยวข้องกับเนื้อหาที่เป็นกิริยาช่วย ดังนั้นให้เพิ่มไว้ด้านล่างส่วนก่อนหน้า:

<ng-template #template>
  <div class="modal-header">
    <h4 class="modal-title pull-left">{{ isNew ? "New User" : "Update User" }}</h4>
    <button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
  <div class="modal-body">
    <form [formGroup]="userForm" (ngSubmit)="isNew ? createUser() : updateUser()" novalidate>
      <input type="hidden" formControlName="id" class="form-control" />
      <div class="form-group">
        <label>Name</label>
        <input type="text" formControlName="name" class="form-control" />
      </div>
      <div class="form-group">
        <label>Age</label>
        <input type="text" formControlName="age" class="form-control" />
      </div>
      <div class="form-group">
        <label>Address</label>
        <textarea class="form-control" formControlName="address" rows="3"></textarea>
      </div>

      <button type="submit" class="btn btn-primary">Submit</button>
    </form>
  </div>
</ng-template>

โปรดทราบว่าเรากำลังใช้ <ng-template> ซึ่งช่วยให้คุณสามารถยึดองค์ประกอบระหว่าง HTML และ Angular รหัสเทมเพลตจะอยู่หลัง # ลงชื่อ

ภายในแบบฟอร์ม โปรดทราบว่าเรากำลังใช้ isNew ตัวแปรส่วนประกอบเพื่อตรวจสอบว่าการใช้งานปัจจุบันของแบบฟอร์มนี้เกี่ยวข้องกับการสร้างหรืออัปเดตของผู้ใช้หรือไม่

สุดท้ายต้องฉีด hello_angular . ทั้งหมด ลงใน Rails index.html.erb หน้าหนังสือ. ดังนั้น เปิดไฟล์นี้ภายใต้ views/users/ โฟลเดอร์และเปลี่ยนเนื้อหาดังต่อไปนี้:

<hello-angular>We're almost done...</hello-angular> <%= javascript_pack_tag 'hello_angular' %>

โมดูลเชิงมุม

ตอนนี้ เราต้องบอก Angular ว่าจะหาข้อมูลได้ที่ไหน มันเกิดขึ้นภายในการกำหนดค่าของโมดูล

เริ่มต้นด้วยการเพิ่มเนื้อหาใน app-bootstrap.module.ts :

import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";

import { ModalModule } from "ngx-bootstrap/modal";

@NgModule({
  imports: [CommonModule, ModalModule.forRoot()],
  exports: [ModalModule],
})
export class AppBootstrapModule {}

สิ่งนี้ถูก จำกัด ไว้ที่ส่วนประกอบ Bootstrap ที่เราได้รับมาจาก ngx-bootstrap องค์ประกอบเดียวที่เราใช้ในตอนนี้คือ Bootstrap modal

จากนั้น เปิด app-routing.module.ts ไฟล์และเปลี่ยนเนื้อหาดังต่อไปนี้:

import { RouterModule, Routes } from "@angular/router";
import { NgModule } from "@angular/core";

import { UserIndexComponent } from "./user/index/index.component";

const appRoutes: Routes = [
  { path: "users", component: UserIndexComponent },
  { path: "", redirectTo: "/users", pathMatch: "full" },
];

@NgModule({
  imports: [RouterModule.forRoot(appRoutes, { scrollPositionRestoration: "enabled" })],
  exports: [RouterModule],
})
export class AppRoutingModule {}

สิ่งนี้จะช่วยให้มั่นใจได้ว่า Angular ตรงกับองค์ประกอบของ User ที่ถูกต้องเมื่อ /users เส้นทางถูกเรียก

และสุดท้าย ลงทะเบียนทั้งหมดภายใน AppModule . หลัก ระดับ. เปิด app.module.ts ไฟล์และตรวจสอบว่ามีลักษณะดังนี้:

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";

import { AppComponent } from "./app.component";
import { AppRoutingModule } from "./app-routing.module";
import { AppBootstrapModule } from "./app-boostrap.module";
import { UserIndexComponent } from "./user/index/index.component";

@NgModule({
  declarations: [AppComponent, UserIndexComponent],
  imports: [HttpClientModule, AppRoutingModule, BrowserModule, FormsModule, ReactiveFormsModule, AppBootstrapModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

ที่นี่ทุกอย่างถูกแมป จากแบบฟอร์มของเรา ไคลเอนต์ HTTP และส่วนประกอบผู้ใช้ไปจนถึงการกำหนดค่าโมดูล Bootstrap และการกำหนดเส้นทาง

เสร็จสิ้นการกำหนดค่า

ก่อนที่เราจะเข้าสู่การทดสอบ เราต้องทำให้เสร็จก่อน เริ่มด้วย app.component.ts ไฟล์:

import { Component } from "@angular/core";

@Component({
  selector: "hello-angular",
  template: "<router-outlet></router-outlet>",
})
export class AppComponent {
  name = "Angular!";
}

ส่วนประกอบหลักของแอพจำเป็นต้องรู้วิธีกำหนดเส้นทางของพาธ ดังนั้น RouterOutlet จะทำงาน

จากนั้น เราต้องตรวจสอบให้แน่ใจว่า Webpacker เข้าใจส่วนขยาย HTML ที่เรากำลังดำเนินการอยู่ สำหรับสิ่งนี้ ให้เปิด webpacker.yml ไฟล์และภายใต้ /config โฟลเดอร์ ค้นหา ส่วนขยาย และเพิ่มรายการต่อไปนี้:

- .html

Webpacker รู้จักเฉพาะตัวโหลด TypeScript ในตัวที่มาพร้อมกับ Angular โดยค่าเริ่มต้น เราจำเป็นต้องประมวลผล HTML ซึ่งเป็นสาเหตุที่เราติดตั้ง html-loader . ไว้ก่อนหน้านี้ การพึ่งพาอาศัยกัน หากต้องการตั้งค่า ให้เปิด environment.js ไฟล์ภายใต้ config/webpack โฟลเดอร์ และเพิ่มการกำหนดค่าตัวโหลดต่อไปนี้:

environment.loaders.append("html", {
  test: /\.html$/,
  use: [
    {
      loader: "html-loader",
      options: {
        minimize: true,
      },
    },
  ],
});

สุดท้ายนี้ เพื่อป้องกันไม่ให้บริการ Angular ของเราได้รับข้อผิดพลาดในการเรียก HTTP เราจำเป็นต้องปิดใช้งานการตรวจสอบโทเค็น CSRF ที่ดำเนินการโดย Rails สำหรับสิ่งนี้ ให้เปิด application_controller.rb ไฟล์ภายใต้ app/controllers โฟลเดอร์และเปลี่ยนเนื้อหาดังต่อไปนี้:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end

กำลังทดสอบ

แค่นั้นแหละ! ดูเหมือนยุ่งยากเล็กน้อยเนื่องจากการตั้งค่าต้องการการปรับแต่งจำนวนมาก แต่ผลลัพธ์ก็คุ้มค่า

ในการทดสอบ บันทึกทุกอย่าง และเริ่มเซิร์ฟเวอร์โดยออก rails s คำสั่ง

จากนั้นไปที่เว็บเบราว์เซอร์ของคุณและพิมพ์ที่อยู่ https://localhost:3000/users ลองใช้เว็บแอปพลิเคชัน CRUD เลย

บทสรุป

เป็นถนนที่ยาวมากในการทำให้ CRUD นี้ใช้งานได้ หลังจากการทดลองใช้ครั้งแรก คุณจะเห็นว่าสิ่งต่างๆ ง่ายขึ้นสำหรับโครงการในอนาคตของคุณ ฉันหวังว่าโครงการนี้จะช่วยในการสร้างจุดเริ่มต้นสำหรับผู้ที่ต้องการเริ่มโครงการอย่างรวดเร็วโดยเข้าร่วมเทคโนโลยีทั้งสอง

แม้ว่าเราจะไม่มีโครงการนั่งร้านแบบโอเพ่นซอร์สเพื่อช่วยในเรื่องนี้ แต่เราก็อาศัยความพยายามของกันและกันในการมีเนื้อหาแบบนั้น ตอนนี้ถึงตาคุณแล้ว แยกโปรเจ็กต์ (หรือสร้างตั้งแต่เริ่มต้น) และเริ่มปรับแต่งของคุณ

ที่เก็บ GitHub สำหรับตัวอย่างนี้สามารถพบได้ที่นี่ ขอให้สนุก!