Tiếng Việt

Lời mở đầu

Chào anh em coder ghé thăm blog của mình! Hôm nay mình sẽ chia sẻ một "bí kíp võ công" về cách setup một dự án Angular hiện đại phiên bản 2025. Mình đảm bảo sau khi đọc xong, anh em sẽ đọc xong!😎

Thôi chúng ta đừng lòng vòng như mấy bạn Hải Phòng. Bắt đầu thôi!

Tạo dự án Angular

Bước đầu tiên của hành trình vạn dặm 🚀

Tải Angular CLI

Trước khi "bon chen" vào thế giới Angular, anh em phải có Angular CLI trong máy đã? Mà có rồi thì .. thì phải chịu thôi!!

pnpm install -g @angular/cli

Tạo mới dự án Angular

Giờ tới lúc "khai sinh" dự án Angular rồi! Mở Terminal lên và khóc thôi:

ng new <project-name>

Bụt hiện lên và hỏi:

Which stylesheet format would you like to use?

Chọn loại style bạn hay dùng hay dự án yêu cầu nhé, riêng tôi chọn CSS vì dễ config với tailwind, không warning

Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? (y/N)

Tùy dự án thôi, mà các dự án mình dùng Angular ít khi cần SEO, đỡ phức tạp chọn N nhé

Do you want to create a 'zoneless' application without zone.js?

Hiện tại mình đang cài ver mới nhất là v20. nó sẽ hỏi mình sẽ dùng zoneless không thì nên Y, tuy sẽ gây khó khăn với người mới nhưng sau này up ver nên v21 thì sẽ bắt buộc.

Vậy là xong các bước tạo dự án rồi, giờ trong lúc chờ Install package thì lướt tiktok tý

Xong xuôi, mở IDE lên và truy cập vào project vừa tạo

Cấu hình

Rule cho AI (option)

Nếu anh em có dùng AI để hỏi bài thì thêm luôn rules file tùy IDE. Trên trang chủ Angular có đầy đủ, hoặc bạn có thể vào link này: Rule Files cho AI

Cấu hình tailwind

Một thứ không thể thiếu với dev FE hiện đại, và cũng tùy dự án mọi người dùng nữa

Mình sẽ gắn link docs cho mọi người tham khảo tại đây hoặc đơn giản là copy lệnh dưới đây và chạy:

pnpm add tailwindcss @tailwindcss/postcss postcss --force

Tiếp theo:

# tạo file .postcssrc.json với nội dung:
echo '{ "plugins": { "@tailwindcss/postcss": {} } }' > .postcssrc.json

# thêm @import "tailwindcss"; vào cuối file styles.css
echo '@import "tailwindcss";' >> ./src/styles.css

Ok done!!! Vậy là cofig tailwind xong.

Cấu hình Prettier ESLint

ESLint giống như người bạn khó tính, hay chỉ ra lỗi logic: "Ê, sao mày lại dùng any ở đây?" hay "Component selector phải theo chuẩn kebab-case chứ!". Còn Prettier là người bạn sạch sẽ, chỉ quan tâm đến việc code có thẳng hàng đẹp mắt không, dấu ngoặc có đúng chỗ không.

Hai thằng này đôi khi cãi nhau (conflict rules), nên ta cần phải "làm mai" cho chúng hòa thuận. Đó là lý do ta cài thêm eslint-config-prettiereslint-plugin-prettier để chúng làm việc cùng nhau thay vì đánh nhau! 😄

Bước 1: Cài ESLint cho Angular

ng add @angular-eslint/schematics

Bước 2: Cài Prettier và các plugin cần thiết

pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier

Bước 3: Tạo file cấu hình Prettier

echo '{ "printWidth": 100, "tabWidth": 2, "useTabs": false, "singleQuote": true, "quoteProps": "preserve", "bracketSpacing": true, "semi": false, "htmlWhitespaceSensitivity": "ignore", "trailingComma": "none", "overrides": [ { "files": "*.html", "options": { "parser": "angular" } } ] }' > .prettierrc

Pro tip: Đây là config mình thích, nhưng bạn có thể tùy chỉnh theo gu riêng. Thích dùng tab thay vì space? Đổi "tabs": true. Thích dấu nháy kép? Đổi "singleQuote": false. Nhưng nhớ là chọn xong thì giữ, đừng hôm nay thế này, mai thế kia, code sẽ loạn như... cái chợ! 😅

Bước 4: Tạo file .prettierignore

Có những thứ không cần đẹp - như file node_modules hay dist. Bỏ qua chúng đi!

echo -e "package.json\npackage-lock.json\ndist\nnode_modules\n.angular/cache" > .prettierignore

Bước 5: Kết nối ESLint và Prettier

Update file eslint.config.js (hoặc tương tự):

// @ts-check
const eslint = require("@eslint/js");
const tseslint = require("typescript-eslint");
const angular = require("angular-eslint");
const prettierPlugin = require("eslint-plugin-prettier");
const prettierConfig = require("eslint-config-prettier");

module.exports = tseslint.config(
  {
    files: ["**/*.ts"],
    extends: [
      eslint.configs.recommended,
      ...tseslint.configs.recommended,
      ...tseslint.configs.stylistic,
      ...angular.configs.tsRecommended,
      prettierConfig,
    ],
    plugins: {
      prettier: prettierPlugin,
    },
    processor: angular.processInlineTemplates,
    rules: {
      "@angular-eslint/directive-selector": [
        "error",
        {
          type: "attribute",
          prefix: "app",
          style: "camelCase",
        },
      ],
      "@angular-eslint/component-selector": [
        "error",
        {
          type: "element",
          prefix: "app",
          style: "kebab-case",
        },
      ],
      '@typescript-eslint/no-explicit-any': 'error', // Forbid usage of 'any'
    },
  },
  {
    files: ["**/*.html"],
    extends: [
      ...angular.configs.templateRecommended,
      ...angular.configs.templateAccessibility,
    ],
    rules: {},
  }
);

Giải thích nhanh:

  • prettierConfig để tắt các rule của ESLint conflict với Prettier
  • 'prettier/prettier': 'error' để Prettier chạy như một ESLint rule
  • '@typescript-eslint/no-explicit-any': 'error' - Cấm dùng any! Ai dùng any là người lười type 😏

Bước 6: Thêm lệnh vào package.json

"scripts": {
  "lint:fix": "prettier --write . && ng lint --fix"
},

Sau khi cài xong, nhớ xóa prettier trong package.json

Bước 7: Cấu hình VS Code (option)

{ 
  "editor.formatOnSave": true
}

Từ giờ mỗi khi bạn Ctrl + S (hoặc Cmd + S trên Mac), code tự động đẹp lên!

Lưu ý: Nếu dự án cũ đã có setting khác, cẩn thận khi bật cái này. Có thể sẽ làm "bay màu" format cũ của team đấy!

Cài Husky và Lintstaged

Bạn đã từng commit code lỗi lên Git rồi sau đó phải "fix typo" hay "fix lint"? Đừng lo, ai cũng từng như vậy! Nhưng có một cách để không bao giờ để điều đó xảy ra nữa: Husky + Lint-staged.

Husky là "người gác cổng" Git của bạn. Mỗi khi bạn commit, nó sẽ chặn lại và hỏi: "Ê đợi đã, code đã sạch chưa?". Nếu chưa sạch, commit sẽ bị reject ngay! Nghe có vẻ khắt khe nhưng tin tôi đi, bạn sẽ cảm ơn nó sau này 🙏

Lint-staged là trợ thủ đắc lực của Husky, chỉ kiểm tra những file bạn vừa thay đổi (staged files) thay vì toàn bộ dự án. Nhanh, gọn, hiệu quả!

Bước 1: Cài Husky và Lint-staged

pnpm add -D husky lint-staged

# husky@v9
pnpm husky init

Lệnh này sẽ tạo thư mục .husky và file pre-commit hook. Đây chính là "điểm checkpoint" trước khi code được commit!

Bước 2: Cấu hình Pre-commit Hook

Mở file .husky/pre-commit và sửa như sau:

pnpm dlx lint-staged

Lưu ý: Nhớ thay pnpm dlx bằng package manager bạn đang dùng nhé:

  • pnpm dlx cho pnpm
  • npx cho npm
  • yarn dlx cho yarn

Bước 4: Cấu hình Lint-staged

Thêm config sau vào package.json:

"lint-staged": {
  "src/**/*.{js,jsx,ts,tsx}": [
    "prettier --write",
    "eslint --fix --max-warnings=0"
  ],
  "src/**/*.{html,scss,css,md,mdx,json}": [
    "prettier --write"
  ]
},

🎉 Kết quả cuối cùng Giờ đây, mỗi khi bạn commit:

  • Husky sẽ kích hoạt pre-commit hook
  • Lint-staged sẽ lấy danh sách file đã thay đổi
  • Prettier sẽ format code đẹp mắt
  • ESLint sẽ kiểm tra và fix các lỗi logic
  • Nếu có lỗi không fix được → Commit bị reject → Bạn phải sửa trước khi commit tiếp

Điều này đảm bảo 100% code commit lên Git đều sạch đẹp, không lỗi lint, không format lộn xộn. Team sẽ yêu bạn vì điều này! ❤️

Bonus tip: Nếu đang vội và muốn skip hook (không khuyến khích!), dùng:

git commit --no-verify -m "message"

Và, chỉ dùng khi thực sự cần thiết, đừng lạm dụng!

Commitlint

Mỗi lần mày git log thấy dòng kiểu fix bug, update, final-final-2, là biết project sắp toang. Không ai hiểu commit đó làm gì, rollback khó, auto changelog cũng chịu.

Commitlint sinh ra để ép dev viết commit có quy tắc. Nó kiểm tra message trước khi commit, sai format → fail liền tay.

Format chuẩn (the rule)

type(scope?): subject

Ví dụ:

  • feat(auth): add google login
  • fix(ui): align button center
  • chore: update deps

Mọi người có thể đọc qua docs Commitlint để setup không cứ làm theo các bước sau:

1. Cài package

pnpm add -D @commitlint/{cli,config-conventional}

2. Tạo file cấu hình

Ở root dự án tạo file:

echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs

3. Gắn vào Husky

Tùy theo ver của Huskey hay dùng win và mac cần config khác nhau, mọi người tham khảo thêm docs tại đây

# Add commit message linting to commit-msg hook
echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg
# Windows users should use ` to escape dollar signs
echo "pnpm dlx commitlint --edit `$1" > .husky/commit-msg
Heads Up
  • File husky phải là UTF-8
  • Dùng LF (Unix) chứ đừng CRLF (Windows).

Ngoài ra nếu chạy terminal của git bash không được thì mọi người dùng cmd khác

Và trong package.json phải có(Huskey v9):

"scripts": { "prepare": "husky" },

Environment Variables

ng generate environments

Sau khi chạy lệnh sau thì angular sẽ tạo ra file env và cấu hình angular.json Các bạn có thể tạo thêm các env theo nhu cầu dự án:

src/
  environments/
    environment.ts
    environment.development.ts
    environment.staging.ts
    environment.production.ts

File: src/environments/environment.ts

export const environment = {
  production: false,
  development: false,
  staging: false,
  apiUrl: 'http://localhost:3000/api',
  appName: 'Angular Starter Local',
  enableDebugTools: true,
  logLevel: 'debug'
}

File: src/environments/environment.development.ts

export const environment = {
  production: false,
  development: true,
  staging: false,
  apiUrl: 'https://dev-api.myapp.com/api',
  appName: 'Angular Starter Development',
  enableDebugTools: true,
  logLevel: 'debug'
}

File: src/environments/environment.staging.ts

export const environment = {
  production: false,
  development: false,
  staging: true,
  apiUrl: 'https://staging-api.myapp.com/api',
  appName: 'Angular Starter Staging',
  enableDebugTools: true,
  logLevel: 'info'
}

File: src/environments/environment.production.ts

export const environment = {
  production: true,
  development: false,
  staging: false,
  apiUrl: 'https://api.myapp.com/api',
  appName: 'Angular Starter Production',
  enableDebugTools: false,
  logLevel: 'error'
}

Cấu hình angular.json Sau khi chạy ng generate environments, Angular tự động update angular.json. Nhưng cần thêm staging:


"build": {
  "builder": "@angular/build:application",
  "options": {
    "browser": "src/main.ts",
    "tsConfig": "tsconfig.app.json",
    "assets": [
      {
        "glob": "**/*",
        "input": "public"
      }
    ],
    "styles": ["src/styles.css"]
  },
  "configurations": {
    "development": {
      "optimization": false,
      "extractLicenses": false,
      "sourceMap": true,
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.development.ts"
        }
      ],
      "budgets": [
        {
          "type": "anyComponentStyle",
          "maximumWarning": "4kB",
          "maximumError": "8kB"
        }
      ]
    },
    "staging": {
      "optimization": true,
      "outputHashing": "all",
      "sourceMap": true,
      "extractLicenses": true,
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.staging.ts"
        }
      ],
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "500kB",
          "maximumError": "1MB"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "4kB",
          "maximumError": "8kB"
        }
      ]
    },
    "production": {
      "optimization": true,
      "outputHashing": "all",
      "sourceMap": false,
      "extractLicenses": true,
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.production.ts"
        }
      ],
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "500kB",
          "maximumError": "1MB"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "4kB",
          "maximumError": "8kB"
        }
      ]
    }
  },
  "defaultConfiguration": "development"
},
"serve": {
  "builder": "@angular/build:dev-server",
  "configurations": {
    "development": {
      "buildTarget": "angular-starter:build:development"
    },
    "staging": {
      "buildTarget": "angular-starter:build:staging"
    },
    "production": {
      "buildTarget": "angular-starter:build:production"
    }
  },
  "defaultConfiguration": "development"
},

Alias Paths

Trong tsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@app/*": ["src/app/*"],
      "@core/*": ["src/app/core/*"],
      "@shared/*": ["src/app/shared/*"],
      "@environments/*": ["src/environments/*"],
      "@assets/*": ["src/assets/*"],
      "@models/*": ["src/app/models/*"],
      "@services/*": ["src/app/services/*"],
      "@components/*": ["src/app/components/*"]
    }
  }
}

Cấu trúc dự án

src/
├── app/
│   ├── core/                          # Singleton services, guards, interceptors
│   │   ├── guards/
│   │   │   ├── auth.guard.ts
│   │   │   └── role.guard.ts
│   │   ├── interceptors/
│   │   │   ├── auth.interceptor.ts
│   │   │   ├── error.interceptor.ts
│   │   │   └── loading.interceptor.ts
│   │   ├── services/
│   │   │   ├── api.service.ts
│   │   │   ├── auth.service.ts
│   │   │   ├── storage.service.ts
│   │   │   └── logger.service.ts
│   │   ├── models/
│   │   │   ├── user.model.ts
│   │   │   ├── api-response.model.ts
│   │   │   └── error.model.ts
│   │   ├── interfaces/
│   │   │   ├── auth.interface.ts
│   │   │   └── config.interface.ts
│   │   └── constants/
│   │       ├── api.constants.ts
│   │       └── app.constants.ts
│   │
│   ├── features/                      # Feature modules (lazy loaded)
│   │   ├── auth/
│   │   │   ├── pages/
│   │   │   │   ├── login/
│   │   │   │   ├── register/
│   │   │   │   └── forgot-password/
│   │   │   ├── components/
│   │   │   │   └── auth-form/
│   │   │   ├── services/
│   │   │   │   └── auth-feature.service.ts
│   │   │   ├── store/
│   │   │   │   └── auth.store.ts
│   │   │   └── auth.routes.ts
│   │   │
│   │   ├── dashboard/
│   │   │   ├── pages/
│   │   │   │   ├── overview/
│   │   │   │   └── analytics/
│   │   │   ├── components/
│   │   │   │   ├── chart/
│   │   │   │   └── stats-card/
│   │   │   ├── services/
│   │   │   ├── store/
│   │   │   └── dashboard.routes.ts
│   │   │
│   │   └── user/
│   │       ├── pages/
│   │       │   ├── profile/
│   │       │   └── settings/
│   │       ├── components/
│   │       ├── services/
│   │       ├── store/
│   │       └── user.routes.ts
│   │
│   ├── layout/                        # Layout components
│   │   ├── header/
│   │   ├── footer/
│   │   ├── sidebar/
│   │   └── main-layout/
│   │
│   ├── app.component.ts               # Root component (standalone)
│   ├── app.config.ts                  # Application config
│   └── app.routes.ts                  # Root routes
├── assets/
│   ├── images/
│   ├── icons/
│   ├── fonts/
│   └── i18n/                          # Translations
│       ├── en.json
│       └── vi.json
├── environments/
│   ├── environment.ts
│   ├── environment.development.ts
│   └── environment.staging.ts
├── styles/
│   ├── _variables.scss
│   ├── _mixins.scss
│   ├── _reset.scss
│   └── styles.scss
├── index.html
├── main.ts
└── styles.scss

Posted onwith tags: