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-prettier và eslint-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
- 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