Phải chi mình biết những điều này trước khi Merge Code Bài tập nhóm MEAN Stack.
AngularJS
24
White

huskykun viết ngày 12/05/2019

Phải chi mình biết những điều này trước khi Merge Code Bài tập nhóm MEAN Stack.

Phần này chỉ nói về Angular, mình sẽ viết bài khác về các phần còn lại.

Mục đích cuối cùng của Angular

"Làm cho việc tạo ra ứng dụng Web nhanh, dễ dàng, dễ mở rộng."

Angular Modules - Block

Một ứng dụng sẽ được chia thành các khối nhỏ kết hợp với nhau, mỗi khối này là một Module. Một module bao gốm: components, pipes, directives, services được đóng gói vào thành một khối. Thông thường, mỗi tính năng chính sẽ được chia thành 1 module.

Angular Components - UI

Component liên quan chính đến UI. Nó đóng gói những gì cần để làm một UI nào đó thành một khối có thể tái sử dụng được. Thông thường bao gồm:

  • HTML,CSS code cho UI đó
  • Một class để đóng gói hết tất cả các biến và logic dùng trong UI đó
  • Selector để class bên dưới biết để áp dụng biến và logic vào UI nào

Vậy vấn đề là: data từ UI và class liên kết với nhau như thế nào? Người dùng nhập data vào từ UI thì nó được lưu ở đâu? Data lấy được từ backend được đẩy ra UI như thế nào? => Data Binding.

Super Quan trọng: Mỗi phần riêng trong ứng dụng nên có Component riêng của nó. Component chỉ chịu trách nhiệm cho phần UI mà nó đảm nhiệm.

Tại sao điều này quan trọng?

Giúp cho tổ chức của ứng dụng rõ ràng, rành mạch, không rối ren chồng chéo, gây ức chế cho người merge code, maintain code, mở rộng code, dễ test, dễ tái sử dụng sau này.

Hãy tưởng tượng, chúng ta có 1 Component điều khiển và quản lý 2 khối UI trên giao diện. Ứng dụng nhỏ -> không sao. Tự nhiên khách yêu cầu 1 UI cần được thêm tính năng, UI còn lại để nguyên. Rồi bạn sẽ sửa code cho phần UI cần phải sửa trong Component chung. Sửa -> thay đổi -> có thể gây ra lỗi -> rủi ro, vần vấn đề là sửa không khéo kéo theo cái UI còn lại lỗi luôn -> x2 rủi ro một cách không đáng có. Xong cái app này xong, có app khác cũng cần 1 UI tương tự như 1 trong 2 UI cũ, bạn sẽ kéo cái Component với 2 UI qua?

Vậy thì tốt nhất là cho mỗi đứa cái Component riêng, không đụng chạm gì nhau. Sửa cái nào test cái đấy, không liên lụy cái kia. Đứng ở khía cạnh merge code thì sung sướng hơn nhiều :|

Ví dụ việc chia Component: ứng dụng là hỏi đáp như Quora:

  • Một thanh điều hướng ở trên cùng => NavbarComponent
  • Quản lý các câu hỏi: QuestionsComponent
    • List ra các câu hỏi => ListQuestionsComponent
    • Thêm câu hỏi mới => NewQuestionComponent
    • Xóa câu hỏi đi => DeleteQuestionComponent
  • Mỗi câu hỏi có nhiều câu trả lời => QuestionAnswersComponent
    • List ra các câu trả lời => ListAnswersComponent
    • Thêm câu trả lời mới => NewAnswerComponent
    • Sửa câu trả lời cũ => UpdateAnswerComponent
    • Xóa câu trả lời đi => DeleteAnswerComponent
    • Upvote câu trả lời => UpvoteAnswerComponent
    • Downvote câu trả lời => DownvoteAnswerComponent
  • ...

Hãy tưởng tượng, khi bạn muốn làm app khác mà chỉ có tính năng Upvote -> giống Like của FB, mà không có Downvote -> copy Upvote Component qua xong sửa lại xíu là xong -> EZ?

Như đã nói ở trên Component liên quan chính đến một khối UI nào đó -> mà khối UI đó thường sẽ thay đổi nội dung, chỉ có Layout sẽ na ná như nhau. Ví dụ như hiển thị User Profile của một người dùng thì ai cũng na ná như ai, có avatar, tên, sở thích,... nhưng những thông tin này tùy người dùng mà sẽ khác nhau => khái niệm Templates.

Templates trong Angular nhìn y như HTML thông thường, chỉ là có thêm một số đồ chơi do Angular thêm vào: *ngFor, { { user.name}}, (click), [user] làm cho nó bá cháy hơn, chơi được với SPA.

Vậy coi như Component đã xử lý ngon lành phần UI, vậy data của User lấy từ API để đẩy lên Templates để hiển thị giải quyết sao? Viết luôn vào file xxx.component.ts được không? Vì dù sao nó cũng liên quan đến cục UI hiển thị thông tin người dùng. Được, tất nhiên là được, dăm ba dòng gọi HTTP Request. Mình từng nghĩ vậy và làm vậy. Nhưng sau này mới nhận ra không nên làm vậy.

Angular Service

Component class, nên gọn gàng. Nhiệm vụ của nó là xử lý UI, và chỉ nên như vậy thôi. Nó không nên dùng để lấy data từ server, validate thông tin người dùng nhập vào... Các tác vụ riêng như vậy, nên để cho Service giải quyết.

Hãy lấy một ví dụ đơn giản: Bạn đi ăn nhà hàng.

Component: Adam, 21 tuổi, quản lý trưởng việc phục vụ ở lầu trên

  • Adam có nên chỉ đạo phục vụ bàn cho lầu dưới? Không. Cách sắp xếp bàn ghế khác nhau, khách lầu dưới hầu hết là khách bình dân trong khi khách lầu trên hầu hết là người Việt Nam sang trọng, Adam chỉ biết tiếng Việt,... Adam hãy làm tốt nhất công việc cho khu vực mình được đảm nhiệm.
  • Adam có nên chạy vào bếp lấy món ăn đã được chuẩn bị ra đưa cho phục vụ bàn và chỉ đạo hãy chuyển đến bàn A, bàn B,...? Không. Adam hãy tập trung quan sát và chỉ đạo Eva (Service) vào bếp lấy món ăn. Eva tự biết dọn đồ ăn kèm, trang trí, đưa vào đúng vị trí bàn chờ,... Giờ Adam chỉ đạo nhân viên phục vụ (Data Binding) lấy món ăn ở vị trí đó cho bàn đó.

Hãy như Adam. :3

Trong ứng dụng Angular, Service giải quyết vấn đề gọi API, chuẩn hóa dữ liệu, validate thông tin người dùng nhập vào,... Các Component sẽ gọi đến các Service này để giao việc tương ứng.

Angular Project Structure

Mình sẽ không nói về chi tiết các folder, file và tác dụng của chúng. Mình sẽ chỉ note một số mà mình cho là quan trọng:

  • /src/: Folder quan trọng nhất, chứa tất cả source code và là nơi làm việc chính
  • /e2e/: Cho việc End-to-End testing
  • node_modules/: Chứa thư viện, thường khá nặng, nên bỏ vào .gitignore. Khi cài đặt thư viện dùng npm, nó sẽ đưa vào đây.
  • package.json: Quản lý tên thư viện và version của thư viện. Rất quan trọng. Có file này -> chạy npm install thì nó sẽ tự cài lại tất cả thư viện vào *node_modules, cho nên node_modules có thể bỏ qua cho đỡ tốn dung lượng khi đưa lên Github, dev khác clone source code về cũng nhanh, file này sẽ giúp tạo node_modules lại chỉ với 1 lệnh.
  • tsconfig.json: Tùy chỉnh Typescript. Chúng ta nên viết app bằng Typescript, sau đó Angular sẽ build ra Javascript theo config trong file này.

Vào bên trong folder /src/:

  • /app: Tất cả component, modules, pages, service,... ở trong này
  • /environtments: quản lý các biến môi trường. Ví dụ như khi dev chúng ta sử dụng local database, khi đưa ra sản phẩm sử dụng database server riêng, hãy định nghĩa trong file này. Khi sử dụng lệnh ng serve để build và dev, Angular tự biết dùng các biến ở môi trường dev. Khi chạy lệnh ng build --prod để build sản phẩm cuối, Angular tự biết dùng biến ở môi trường production.
  • /assets: chứa hình ảnh, sample-data json, ...

Angular best pratices: /app folder

Dưới đây là một số gợi ý, sẽ giúp bạn tổ chức code rõ ràng, dễ đọc, dễ hiểu hơn, dựa vào những gì đã nói bên trên. Các file và folder sau nên có trong thư mục app/:

  • /shared:
    • Chứa các SharedModule: các components, directives, pipes thông thường mà các module khác thường cần dùng đến, để share cho các module khác dùng. Ví dụ các UI đều có điểm chung là sử dụng thanh Navbar để điều hướng qua lại giữa các giao diện khác nhau, thì NavbarCompont có thể được để trong folder này.
    • Bạn cũng nên tạo SharedModule để import các thư viện cần thiết, hay được dùng lặp đi lặp lại ở các Module khác như CommonModule, FormsModule, SharedModule, Material Design,... sau đó export nó ra. Các Module khác khi cần dùng không cần phải import lại các Module chung này ở mỗi Module riêng mà chỉ cần import SharedModule là đủ.
  • /styles:
    • Chứa các style mà được dùng ở nhiều Component khác nhau. Để file scss ở các Component đó có thể import.
  • app.component.html:
    • Chỉ nên chứa và các Component được dùng ở mọi UI, như Navbar chẳng hạn
  • app.routing.module.ts:
    • Chỉ định nghĩa main routes. Các routes con (nếu bạn sử dụng lazy modules) nên được định nghĩa bên trong các Module con đó.
  • app.module.ts:
    • Chứa tất cả các Module chính của app, được dùng để bootstrap app lúc khởi động app

Navigation và Routing

Tips: Bạn có thể xem xét sử dụng Breadcrumbs để navigate trở lại các pages trước cho dễ dàng.

Cái này không thuộc nhiều về mặt kỹ thuật mà về cách mà bạn design ứng dụng của bạn, mỗi page nên hiển thị cái gì, chuyển qua lại các page ra sao cho người dùng dễ dàng sử dụng nhất,...

Trong app.routes.ts nên chứa code như sau:

import { Routes } from '@angular/router';

export const routes: Routes = [
    {
        path: '',
        component: CategoriesComponent,
          resolve: {
            data: CategoriesResolver
            }
    },
    {
        path: 'questions/about/:categorySlug',
        component: CategoryQuestionsComponent,
        resolve: {
          data: CategoryQuestionsResolver
        }
    },
    {
        path: 'question/:questionSlug',
        component: QuestionAnswersComponent,
        resolve: {
          data: QuestionAnswersResolver
        }
    }
];

Chú ý phần resolve. Nó được dùng để pre-fetch data cho Component trước khi route tới Component đó được active. Nó giúp chúng ta chắc chắn được rằng data mà Component đó cần dùng đã được chuẩn bị sẵn sàng trước khi Component đó được hiển thị, tránh trường hợp Component hiển thị data trống.

Enhance UI/UX

Bạn có thể sử dùng Angular Material, ngx-bootstrap hoặc Bulma để giúp việc làm UI/UX đẹp và nhẹ nhàng hơn nhiều.

Thêm Backend cho ứng dụng

Sử dụng Models và Services giúp chúng ta giải quyết được vấn đề tách rời Frontend và Backend. Ứng dụng của chúng ta không cần quan tâm Backend được implement như thế nào, sử dụng Express hay Python Dijango, PHP,..., nó chỉ cần API để gọi vào và lấy data, sau đó tiền xử lý một chút và đẩy ra UI.

Domain Models

Ý tưởng ở đây là đưa các business logic ra khỏi Component, đưa vào các class (thường gọi là các model), để các business logic này có thể được tái sử dụng nếu ví dụ, chẳng may chúng ta xài React thay vì Angular. Chỉ cách thức tạo ra UI là thay đổi, còn data và business logic có thể được tái sử dụng. Bạn có thể đọc thêm về Angular Domain Models.

Data Services

Một đoạn code mẫu về cách kết hợp Model và Service:

//in category.model.ts
export class CategoryModel {
  slug: string;
  title: string;
  image: string;
  description: string;
  tags: Array<Object>;
}

//in categories.service.ts
getCategories(): Promise<CategoryModel[]> {
  return this.http.get("./assets/categories.json")
  .toPromise()
  .then(res => res.json() as CategoryModel[])
}

Cách sử dụng Resolve:

//in categories.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from "@angular/router";
import { CategoriesService } from "../services/categories.service";

@Injectable()
export class CategoriesResolver implements Resolve<any> {

  constructor(private categoriesService: CategoriesService) { }

  resolve() {
    return new Promise((resolve, reject) => {

      let breadcrumbs = [
        { url: '/', label: 'Categories' }
      ];

      //get categories from local json file
      this.categoriesService.getCategories()
      .then(
        categories => {
          return resolve({
            categories: categories,
            breadcrumbs: breadcrumbs
          });
        },
        err => {
          return resolve(null);
        }
      )
    });
  }
}

Theo document của Angular, chúng ta có 2 cách để đăng kí Service provider: trong Component hoặc trong Module. Và cách tốt nhất là nên dùng app.module.ts
Cho Angular biết cách sử dụng các Service này:

//in app.module.ts
@NgModule({
  declarations: [
    AppComponent,
    CategoriesComponent,
    CategoryQuestionsComponent,
    NewQuestionModalComponent,
    NewAnswerModalComponent,
    UpdateAnswerModalComponent,
    QuestionAnswersComponent,
    DeleteQuestionModalComponent,
    DeleteAnswerModalComponent
  ],
  imports: [
    RouterModule.forRoot(routes,
      { useHash: false }
    ),
    SharedModule
  ],
  entryComponents: [

  ],
  providers: [
    CategoriesService,
    QuestionsService,
    AnswersService,
    CategoryQuestionsResolver,
    CategoriesResolver,
    QuestionAnswersResolver
  ],
  bootstrap: [AppComponent]
})

export class AppModule { }

Bạn có thể đọc thêm về: Dependency Injection

Final

Đó là về Frontend, bạn có thể tìm hiểu thêm về Loopback hoặc FeatherJS để làm API backend cho ứng dụng.

Never stop learning.

P/s: Chút ít kinh nghiệm rút ra khi merge code đồng đội

Trên đây là những gì mình tìm hiểu được và qua quá trình merge code đồng đội, mình để ý thấy một số tips mà nếu biết trước sẽ tránh đau đớn không cần thiết:

  • Áp dụng triệt để những nguyên tắc bên trên
  • Thống nhất trước những gì chung như database, API sẽ như thế nào? Gửi cái gì? Trả về cái gì?
  • Project structure sẽ như thế nào?
  • Có milestone rõ ràng

Nếu biết trước và thống nhất những điều này thì giờ không merge code sml @.@

Merge_code

Đây là nơi mình học được những điều này, source mẫu cũng tuân thủ những nguyên tắc trên.

https://github.com/AngularTemplates/learn-angular-from-scratch-step-by-step

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

huskykun

2 bài viết.
8 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
18 1
DevOps6 series NOTE: Đây là Phần 1 trong chuỗi bài viết của mình Ai nên đọc tiếp Nếu anh em là dev muốn sự nghiệp của mình lái nhẹ sang hướng k...
huskykun viết 4 tháng trước
18 1
Bài viết liên quan
White
12 11
Khi các bạn viết sử dụng AngularJS có thấy thắc mắc về phần làm thế nào để mình viết 1 function mà có thể sử dụng cho toàn bộ app của mình không? V...
My Mai viết hơn 4 năm trước
12 11
White
7 2
1. Giới thiệu: Lovefield, một relational database được viết hoàn toàn bởi JavaScript & được phát triển bởi Google. Cung cấp cú pháp truy vấn tư...
Cùi Bắp viết hơn 3 năm trước
7 2
White
21 5
Tạo ứng dụng chat với 50 dòng code, Firebase và AngularJS Từ lúc viết blog tới giờ, mình chưa có bài nào hướng dẫn các bạn tạo ra một sản phẩm từ ...
Huy Hoàng Phạm viết gần 4 năm trước
21 5
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
2 bài viết.
8 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!