front-end-dev-standards 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/validate-package.js +10 -5
- package/standards//342/232/241 performance.md" +138 -0
- package/standards//360/237/205/260/357/270/217 angular.md" +571 -0
- package/standards//360/237/216/250 ui-ux.md" +391 -0
- package/standards//360/237/223/232 documentation.md" +127 -0
- package/standards//360/237/224/214 api-standards.md" +201 -0
- package/standards//360/237/224/220 security.md" +432 -0
- package/standards//360/237/232/200 ci-cd.md" +134 -0
- package/standards//360/237/244/226 ai-development.md" +142 -0
- package/standards//360/237/247/221/342/200/215/360/237/222/273 coding-style.md" +765 -0
- package/standards//360/237/247/252 testing.md" +399 -0
- package/standards//360/237/247/261 architecture.md" +654 -0
- package/standards/angular.md +0 -234
- package/standards/architecture.md +0 -263
- package/standards/coding-style.md +0 -223
- package/standards/security.md +0 -6
- package/standards/testing.md +0 -244
package/standards/angular.md
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
# Angular Standards
|
|
2
|
-
|
|
3
|
-
> Company-wide Angular conventions for AI-assisted and human-written code.
|
|
4
|
-
> Target: Angular 20+ (standalone-first, signals-first).
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Core Principles
|
|
9
|
-
|
|
10
|
-
1. **Standalone only** — no `NgModule` for new features.
|
|
11
|
-
2. **Signals first** — prefer `signal()`, `computed()`, `effect()` over RxJS for local state.
|
|
12
|
-
3. **Functional DI** — use `inject()` instead of constructor injection.
|
|
13
|
-
4. **OnPush everywhere** — default change detection strategy for all components.
|
|
14
|
-
5. **Typed everything** — strict TypeScript, typed reactive forms, typed HTTP responses.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Components
|
|
19
|
-
|
|
20
|
-
### Required Pattern
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
|
24
|
-
import { EmployeeService } from '../services/employee.service';
|
|
25
|
-
import { Employee } from '../models/employee.model';
|
|
26
|
-
|
|
27
|
-
@Component({
|
|
28
|
-
selector: 'app-employee-list',
|
|
29
|
-
imports: [/* standalone imports only */],
|
|
30
|
-
templateUrl: './employee-list.component.html',
|
|
31
|
-
styleUrl: './employee-list.component.scss',
|
|
32
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
33
|
-
})
|
|
34
|
-
export class EmployeeListComponent {
|
|
35
|
-
private readonly employeeService = inject(EmployeeService);
|
|
36
|
-
|
|
37
|
-
readonly employees = signal<Employee[]>([]);
|
|
38
|
-
readonly loading = signal(false);
|
|
39
|
-
readonly error = signal<string | null>(null);
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Rules
|
|
44
|
-
|
|
45
|
-
| Do | Don't |
|
|
46
|
-
|---|---|
|
|
47
|
-
| Standalone components | `NgModule`-based components |
|
|
48
|
-
| `inject()` for DI | Constructor injection |
|
|
49
|
-
| `signal()` / `computed()` for state | `BehaviorSubject` for simple UI state |
|
|
50
|
-
| `ChangeDetectionStrategy.OnPush` | Default change detection |
|
|
51
|
-
| `input()` / `output()` signal APIs | `@Input()` / `@Output()` decorators (legacy) |
|
|
52
|
-
| `host: { class: '...' }` or `:host` in SCSS | Inline styles unless dynamic |
|
|
53
|
-
| Separate `.html`, `.scss`, `.ts` files | Inline templates for complex UI |
|
|
54
|
-
|
|
55
|
-
### File Naming
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
employee-list.component.ts
|
|
59
|
-
employee-list.component.html
|
|
60
|
-
employee-list.component.scss
|
|
61
|
-
employee-list.component.spec.ts
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Selector prefix: `app-` (application) or feature prefix e.g. `hr-` for HRMS.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Services
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import { Injectable, inject } from '@angular/core';
|
|
72
|
-
import { HttpClient } from '@angular/common/http';
|
|
73
|
-
import { Observable } from 'rxjs';
|
|
74
|
-
import { Employee } from '../models/employee.model';
|
|
75
|
-
|
|
76
|
-
@Injectable({ providedIn: 'root' })
|
|
77
|
-
export class EmployeeService {
|
|
78
|
-
private readonly http = inject(HttpClient);
|
|
79
|
-
private readonly baseUrl = '/api/employees';
|
|
80
|
-
|
|
81
|
-
getAll(): Observable<Employee[]> {
|
|
82
|
-
return this.http.get<Employee[]>(this.baseUrl);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Rules
|
|
88
|
-
|
|
89
|
-
- Use `providedIn: 'root'` unless scoped to a route or component.
|
|
90
|
-
- Return typed `Observable<T>` or use `httpResource()` for signal-based HTTP (Angular 19+).
|
|
91
|
-
- Keep services thin — business logic belongs in dedicated use-case/domain services when complex.
|
|
92
|
-
- No `HttpClient` calls directly from components.
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Routing
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import { Routes } from '@angular/router';
|
|
100
|
-
|
|
101
|
-
export const EMPLOYEE_ROUTES: Routes = [
|
|
102
|
-
{
|
|
103
|
-
path: '',
|
|
104
|
-
loadComponent: () =>
|
|
105
|
-
import('./pages/employee-list/employee-list.page').then(m => m.EmployeeListPage),
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
path: ':id',
|
|
109
|
-
loadComponent: () =>
|
|
110
|
-
import('./pages/employee-detail/employee-detail.page').then(m => m.EmployeeDetailPage),
|
|
111
|
-
},
|
|
112
|
-
];
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Rules
|
|
116
|
-
|
|
117
|
-
- Lazy-load feature routes with `loadComponent`.
|
|
118
|
-
- Use functional guards: `canActivate: [authGuard]`.
|
|
119
|
-
- Route data and params via `input()` bindings where supported.
|
|
120
|
-
- One `*.routes.ts` file per feature.
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Forms
|
|
125
|
-
|
|
126
|
-
Use **Typed Reactive Forms** exclusively for non-trivial forms.
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
130
|
-
|
|
131
|
-
interface EmployeeForm {
|
|
132
|
-
firstName: FormControl<string>;
|
|
133
|
-
lastName: FormControl<string>;
|
|
134
|
-
email: FormControl<string>;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
@Component({
|
|
138
|
-
imports: [ReactiveFormsModule],
|
|
139
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
140
|
-
})
|
|
141
|
-
export class EmployeeFormComponent {
|
|
142
|
-
readonly form = new FormGroup<EmployeeForm>({
|
|
143
|
-
firstName: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
144
|
-
lastName: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
145
|
-
email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }),
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Rules
|
|
151
|
-
|
|
152
|
-
| Do | Don't |
|
|
153
|
-
|---|---|
|
|
154
|
-
| Typed `FormGroup<T>` | Untyped forms |
|
|
155
|
-
| `nonNullable: true` on controls | Nullable controls without reason |
|
|
156
|
-
| Reactive forms for complex UI | Template-driven forms for multi-field forms |
|
|
157
|
-
| Angular Material form controls | Custom unstyled inputs in enterprise apps |
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## State Management
|
|
162
|
-
|
|
163
|
-
**Default:** component-level signals + services.
|
|
164
|
-
|
|
165
|
-
**Use NgRx / SignalStore when:**
|
|
166
|
-
|
|
167
|
-
- State is shared across multiple features.
|
|
168
|
-
- Complex async orchestration is required.
|
|
169
|
-
- Time-travel debugging or audit trails are needed.
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
import { signalStore, withState, withMethods } from '@ngrx/signals';
|
|
173
|
-
|
|
174
|
-
export const EmployeeStore = signalStore(
|
|
175
|
-
{ providedIn: 'root' },
|
|
176
|
-
withState({ employees: [] as Employee[], loading: false }),
|
|
177
|
-
withMethods(/* ... */),
|
|
178
|
-
);
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## HTTP & Error Handling
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
// Interceptor pattern — centralized error handling
|
|
187
|
-
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
|
|
188
|
-
return next(req).pipe(
|
|
189
|
-
catchError((error: HttpErrorResponse) => {
|
|
190
|
-
// log, toast, rethrow
|
|
191
|
-
return throwError(() => error);
|
|
192
|
-
}),
|
|
193
|
-
);
|
|
194
|
-
};
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
- Always type HTTP responses: `http.get<Employee[]>()`.
|
|
198
|
-
- Use functional interceptors (`HttpInterceptorFn`).
|
|
199
|
-
- Display user-friendly errors via a shared `NotificationService`.
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## Angular Material
|
|
204
|
-
|
|
205
|
-
- Use Material 3 theming (`@angular/material`).
|
|
206
|
-
- Prefer CDK utilities (Overlay, A11y, DragDrop) over third-party equivalents.
|
|
207
|
-
- Wrap Material components in feature-specific wrappers when repeated patterns emerge.
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
import { MatButtonModule } from '@angular/material/button';
|
|
211
|
-
import { MatTableModule } from '@angular/material/table';
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## Performance
|
|
217
|
-
|
|
218
|
-
- `track` in `@for` loops: `@for (item of items(); track item.id)`.
|
|
219
|
-
- Avoid unnecessary subscriptions — use `async` pipe or signals.
|
|
220
|
-
- Lazy load routes and heavy components.
|
|
221
|
-
- Use `NgOptimizedImage` for static images.
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## Anti-Patterns (Never Generate)
|
|
226
|
-
|
|
227
|
-
- ❌ `NgModule` declarations for new code
|
|
228
|
-
- ❌ Constructor-based dependency injection
|
|
229
|
-
- ❌ `BehaviorSubject` for simple component state
|
|
230
|
-
- ❌ `any` type without explicit justification
|
|
231
|
-
- ❌ Direct DOM manipulation (`document.querySelector`)
|
|
232
|
-
- ❌ Business logic inside components (beyond UI orchestration)
|
|
233
|
-
- ❌ Unsubscribed manual subscriptions
|
|
234
|
-
- ❌ Default change detection on presentational components
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
# Architecture Standards
|
|
2
|
-
|
|
3
|
-
> Feature-based, domain-driven frontend architecture for enterprise Angular applications.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## High-Level Structure
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
src/
|
|
11
|
-
├── app/
|
|
12
|
-
│ ├── core/ # Singleton services, guards, interceptors (app-wide)
|
|
13
|
-
│ │ ├── auth/
|
|
14
|
-
│ │ ├── interceptors/
|
|
15
|
-
│ │ ├── guards/
|
|
16
|
-
│ │ └── services/
|
|
17
|
-
│ ├── shared/ # Reusable UI components, pipes, directives
|
|
18
|
-
│ │ ├── components/
|
|
19
|
-
│ │ ├── directives/
|
|
20
|
-
│ │ ├── pipes/
|
|
21
|
-
│ │ └── models/
|
|
22
|
-
│ ├── features/ # Business feature modules (lazy-loaded)
|
|
23
|
-
│ │ ├── employees/
|
|
24
|
-
│ │ │ ├── components/
|
|
25
|
-
│ │ │ ├── pages/
|
|
26
|
-
│ │ │ ├── services/
|
|
27
|
-
│ │ │ ├── models/
|
|
28
|
-
│ │ │ ├── employees.routes.ts
|
|
29
|
-
│ │ │ └── index.ts
|
|
30
|
-
│ │ └── payroll/
|
|
31
|
-
│ ├── layout/ # Shell, header, sidebar, footer
|
|
32
|
-
│ ├── app.config.ts
|
|
33
|
-
│ ├── app.routes.ts
|
|
34
|
-
│ └── app.ts
|
|
35
|
-
├── environments/
|
|
36
|
-
└── styles/
|
|
37
|
-
├── _variables.scss
|
|
38
|
-
├── _mixins.scss
|
|
39
|
-
└── styles.scss
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Layer Responsibilities
|
|
45
|
-
|
|
46
|
-
### Core Layer
|
|
47
|
-
|
|
48
|
-
**Purpose:** Application-wide infrastructure used once.
|
|
49
|
-
|
|
50
|
-
| Contains | Does NOT contain |
|
|
51
|
-
|---|---|
|
|
52
|
-
| Auth service & guards | Feature-specific business logic |
|
|
53
|
-
| HTTP interceptors | UI components |
|
|
54
|
-
| Global error handler | Feature models |
|
|
55
|
-
| App initializer | |
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
core/
|
|
59
|
-
auth/
|
|
60
|
-
auth.service.ts
|
|
61
|
-
auth.guard.ts
|
|
62
|
-
interceptors/
|
|
63
|
-
auth.interceptor.ts
|
|
64
|
-
error.interceptor.ts
|
|
65
|
-
services/
|
|
66
|
-
notification.service.ts
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Shared Layer
|
|
70
|
-
|
|
71
|
-
**Purpose:** Reusable, presentation-focused building blocks with no feature coupling.
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
shared/
|
|
75
|
-
components/
|
|
76
|
-
confirm-dialog/
|
|
77
|
-
data-table/
|
|
78
|
-
page-header/
|
|
79
|
-
models/
|
|
80
|
-
api-response.model.ts
|
|
81
|
-
pagination.model.ts
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**Rules:**
|
|
85
|
-
|
|
86
|
-
- Shared components must not import from `features/`.
|
|
87
|
-
- Inputs/outputs only — no direct service calls to feature services.
|
|
88
|
-
- Document inputs with JSDoc when non-obvious.
|
|
89
|
-
|
|
90
|
-
### Features Layer
|
|
91
|
-
|
|
92
|
-
**Purpose:** Self-contained business capabilities.
|
|
93
|
-
|
|
94
|
-
Each feature is a vertical slice:
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
features/employees/
|
|
98
|
-
components/ # Feature-specific presentational components
|
|
99
|
-
employee-card/
|
|
100
|
-
employee-form/
|
|
101
|
-
pages/ # Routable smart components (containers)
|
|
102
|
-
employee-list/
|
|
103
|
-
employee-detail/
|
|
104
|
-
services/ # Feature API & state services
|
|
105
|
-
employee.service.ts
|
|
106
|
-
models/ # Feature domain types
|
|
107
|
-
employee.model.ts
|
|
108
|
-
employee-filter.model.ts
|
|
109
|
-
employees.routes.ts # Feature routing
|
|
110
|
-
index.ts # Public API barrel (optional)
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Smart vs Presentational Components
|
|
116
|
-
|
|
117
|
-
### Presentational (Dumb)
|
|
118
|
-
|
|
119
|
-
- Receives data via `input()`.
|
|
120
|
-
- Emits events via `output()`.
|
|
121
|
-
- No injected feature services.
|
|
122
|
-
- OnPush change detection.
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
@Component({
|
|
126
|
-
selector: 'app-employee-card',
|
|
127
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
128
|
-
})
|
|
129
|
-
export class EmployeeCardComponent {
|
|
130
|
-
readonly employee = input.required<Employee>();
|
|
131
|
-
readonly selected = output<Employee>();
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Smart (Container / Page)
|
|
136
|
-
|
|
137
|
-
- Injects services.
|
|
138
|
-
- Manages state with signals.
|
|
139
|
-
- Passes data down to presentational components.
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
@Component({
|
|
143
|
-
selector: 'app-employee-list-page',
|
|
144
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
145
|
-
})
|
|
146
|
-
export class EmployeeListPage {
|
|
147
|
-
private readonly employeeService = inject(EmployeeService);
|
|
148
|
-
readonly employees = signal<Employee[]>([]);
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Naming:** routable components use `.page.ts` suffix or live in `pages/` folder.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Data Flow
|
|
157
|
-
|
|
158
|
-
```
|
|
159
|
-
┌─────────────┐ HTTP/API ┌─────────────┐
|
|
160
|
-
│ Service │ ◄──────────────► │ Backend │
|
|
161
|
-
└──────┬──────┘ └─────────────┘
|
|
162
|
-
│ signal / observable
|
|
163
|
-
▼
|
|
164
|
-
┌─────────────┐ inputs/outputs ┌──────────────────┐
|
|
165
|
-
│ Page/Smart │ ────────────────► │ Presentational │
|
|
166
|
-
│ Component │ │ Component │
|
|
167
|
-
└─────────────┘ └──────────────────┘
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
- Unidirectional data flow.
|
|
171
|
-
- Pages orchestrate; components render.
|
|
172
|
-
- No two sibling components communicating directly — go through parent or a store.
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## API Integration
|
|
177
|
-
|
|
178
|
-
### Model Conventions
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
/** Domain entity returned by GET /api/employees */
|
|
182
|
-
export interface Employee {
|
|
183
|
-
id: string;
|
|
184
|
-
firstName: string;
|
|
185
|
-
lastName: string;
|
|
186
|
-
email: string;
|
|
187
|
-
departmentId: string;
|
|
188
|
-
createdAt: string; // ISO 8601 from API; map to Date in mappers if needed
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Payload for POST /api/employees */
|
|
192
|
-
export interface CreateEmployeeRequest {
|
|
193
|
-
firstName: string;
|
|
194
|
-
lastName: string;
|
|
195
|
-
email: string;
|
|
196
|
-
departmentId: string;
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
- Separate **entity**, **request DTO**, and **response wrapper** types.
|
|
201
|
-
- Map API shapes to view models in services when UI needs differ from API.
|
|
202
|
-
|
|
203
|
-
### Environment Configuration
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
// environments/environment.ts
|
|
207
|
-
export const environment = {
|
|
208
|
-
production: false,
|
|
209
|
-
apiBaseUrl: 'http://localhost:8080/api',
|
|
210
|
-
};
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Never hardcode API URLs in services.
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Cross-Cutting Concerns
|
|
218
|
-
|
|
219
|
-
| Concern | Location |
|
|
220
|
-
|---|---|
|
|
221
|
-
| Authentication | `core/auth/` |
|
|
222
|
-
| Authorization guards | `core/guards/` |
|
|
223
|
-
| HTTP headers / tokens | `core/interceptors/auth.interceptor.ts` |
|
|
224
|
-
| Global errors | `core/interceptors/error.interceptor.ts` + `NotificationService` |
|
|
225
|
-
| Logging | `core/services/logger.service.ts` |
|
|
226
|
-
| Feature flags | `core/services/feature-flag.service.ts` |
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Barrel Exports
|
|
231
|
-
|
|
232
|
-
Use `index.ts` sparingly at feature boundaries only:
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
// features/employees/index.ts
|
|
236
|
-
export { EMPLOYEE_ROUTES } from './employees.routes';
|
|
237
|
-
export type { Employee } from './models/employee.model';
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Do not create deep barrel files that cause circular dependencies.
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## Dependency Rules
|
|
245
|
-
|
|
246
|
-
```
|
|
247
|
-
features/ ──► shared/ ──► (no upward imports)
|
|
248
|
-
│ │
|
|
249
|
-
└──────► core/ ◄──────┘
|
|
250
|
-
|
|
251
|
-
features/ ──X──► other features/ (use shared events, routing, or stores instead)
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
Enforce with ESLint boundary rules when available.
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## Scalability Guidelines
|
|
259
|
-
|
|
260
|
-
1. **New feature = new folder** under `features/` with its own routes.
|
|
261
|
-
2. **Promote to shared** only after reuse in 2+ features.
|
|
262
|
-
3. **Split features** when a folder exceeds ~15 components or multiple unrelated domains.
|
|
263
|
-
4. **Co-locate tests** next to source files (`*.spec.ts`).
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
# Coding Style Standards
|
|
2
|
-
|
|
3
|
-
> TypeScript and Angular style conventions for consistent, review-friendly code.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## TypeScript
|
|
8
|
-
|
|
9
|
-
### Strict Mode
|
|
10
|
-
|
|
11
|
-
Always enable strict compiler options:
|
|
12
|
-
|
|
13
|
-
```json
|
|
14
|
-
{
|
|
15
|
-
"compilerOptions": {
|
|
16
|
-
"strict": true,
|
|
17
|
-
"noImplicitOverride": true,
|
|
18
|
-
"noPropertyAccessFromIndexSignature": true,
|
|
19
|
-
"noImplicitReturns": true,
|
|
20
|
-
"noFallthroughCasesInSwitch": true
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Type Rules
|
|
26
|
-
|
|
27
|
-
| Rule | Example |
|
|
28
|
-
|---|---|
|
|
29
|
-
| Prefer `interface` for object shapes | `interface Employee { id: string }` |
|
|
30
|
-
| Use `type` for unions/intersections | `type Status = 'active' \| 'inactive'` |
|
|
31
|
-
| Never use `any` | Use `unknown` + type guards |
|
|
32
|
-
| Explicit return types on public methods | `getEmployee(id: string): Observable<Employee>` |
|
|
33
|
-
| Use `readonly` for immutable properties | `readonly id = input.required<string>()` |
|
|
34
|
-
| Use `const` by default | `const items = signal<T[]>([])` |
|
|
35
|
-
|
|
36
|
-
### Naming Conventions
|
|
37
|
-
|
|
38
|
-
| Element | Convention | Example |
|
|
39
|
-
|---|---|---|
|
|
40
|
-
| Classes | PascalCase | `EmployeeService` |
|
|
41
|
-
| Interfaces | PascalCase (no `I` prefix) | `Employee`, not `IEmployee` |
|
|
42
|
-
| Files | kebab-case | `employee-list.component.ts` |
|
|
43
|
-
| Variables / functions | camelCase | `getEmployeeById` |
|
|
44
|
-
| Constants | SCREAMING_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
45
|
-
| Signals | noun or adjective | `loading`, `employees`, `isValid` |
|
|
46
|
-
| Boolean signals | `is/has/can` prefix | `isLoading`, `hasError` |
|
|
47
|
-
| Observables | `$` suffix (when used) | `employees$` |
|
|
48
|
-
| Private fields | `private readonly` | `private readonly http = inject(HttpClient)` |
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Angular Style
|
|
53
|
-
|
|
54
|
-
### Import Order
|
|
55
|
-
|
|
56
|
-
1. Angular core/common
|
|
57
|
-
2. Angular feature modules (router, forms, material)
|
|
58
|
-
3. RxJS
|
|
59
|
-
4. Third-party libraries
|
|
60
|
-
5. Application core
|
|
61
|
-
6. Application shared
|
|
62
|
-
7. Feature-relative imports
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
|
|
66
|
-
import { RouterLink } from '@angular/router';
|
|
67
|
-
import { MatButtonModule } from '@angular/material/button';
|
|
68
|
-
|
|
69
|
-
import { EmployeeService } from '../../services/employee.service';
|
|
70
|
-
import { Employee } from '../../models/employee.model';
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Member Order (inside classes)
|
|
74
|
-
|
|
75
|
-
1. `inject()` / DI fields
|
|
76
|
-
2. `input()` / `output()` declarations
|
|
77
|
-
3. Public signals / computed
|
|
78
|
-
4. Protected template-facing members
|
|
79
|
-
5. Constructor (avoid — use inject)
|
|
80
|
-
6. Lifecycle hooks (alphabetical)
|
|
81
|
-
7. Public methods
|
|
82
|
-
8. Private methods
|
|
83
|
-
|
|
84
|
-
### Template Style
|
|
85
|
-
|
|
86
|
-
```html
|
|
87
|
-
<!-- Use native control flow -->
|
|
88
|
-
@if (loading()) {
|
|
89
|
-
<app-loading-spinner />
|
|
90
|
-
} @else if (error(); as message) {
|
|
91
|
-
<app-error-banner [message]="message" />
|
|
92
|
-
} @else {
|
|
93
|
-
<ul>
|
|
94
|
-
@for (employee of employees(); track employee.id) {
|
|
95
|
-
<li>{{ employee.firstName }} {{ employee.lastName }}</li>
|
|
96
|
-
} @empty {
|
|
97
|
-
<li>No employees found.</li>
|
|
98
|
-
}
|
|
99
|
-
</ul>
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
| Do | Don't |
|
|
104
|
-
|---|---|
|
|
105
|
-
| `@if`, `@for`, `@switch` | `*ngIf`, `*ngFor`, `*ngSwitch` |
|
|
106
|
-
| `track` in `@for` | `@for` without track |
|
|
107
|
-
| Async pipe or signals in template | Manual subscribe + field assignment |
|
|
108
|
-
| `[class.active]="isActive()"` | `[ngClass]` for simple cases |
|
|
109
|
-
|
|
110
|
-
### SCSS Style
|
|
111
|
-
|
|
112
|
-
- Use component-scoped styles (`styleUrl`).
|
|
113
|
-
- Use design tokens / variables from `styles/_variables.scss`.
|
|
114
|
-
- BEM-like naming for complex components: `.employee-card__title`.
|
|
115
|
-
- No `!important` unless overriding third-party styles.
|
|
116
|
-
|
|
117
|
-
```scss
|
|
118
|
-
:host {
|
|
119
|
-
display: block;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.employee-card {
|
|
123
|
-
&__title {
|
|
124
|
-
font-weight: 600;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## Comments & Documentation
|
|
132
|
-
|
|
133
|
-
### When to Comment
|
|
134
|
-
|
|
135
|
-
- **Do** explain *why* for non-obvious business rules.
|
|
136
|
-
- **Do** use JSDoc on public service methods and shared component inputs.
|
|
137
|
-
- **Don't** comment obvious code.
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
/**
|
|
141
|
-
* Fetches employees filtered by department.
|
|
142
|
-
* API returns inactive employees unless `includeInactive` is true.
|
|
143
|
-
*/
|
|
144
|
-
getByDepartment(departmentId: string, includeInactive = false): Observable<Employee[]> {
|
|
145
|
-
// ...
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### TODO Format
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
// TODO(HRMS-1234): Replace mock data with API call after backend deploy
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## Error Handling
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
async loadEmployees(): Promise<void> {
|
|
161
|
-
this.loading.set(true);
|
|
162
|
-
this.error.set(null);
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const data = await firstValueFrom(this.employeeService.getAll());
|
|
166
|
-
this.employees.set(data);
|
|
167
|
-
} catch (err) {
|
|
168
|
-
this.error.set('Failed to load employees. Please try again.');
|
|
169
|
-
console.error('[EmployeeListPage]', err);
|
|
170
|
-
} finally {
|
|
171
|
-
this.loading.set(false);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
- Always handle errors at the page/smart component level.
|
|
177
|
-
- Log with context prefix: `[ClassName]`.
|
|
178
|
-
- Never swallow errors silently.
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## Formatting
|
|
183
|
-
|
|
184
|
-
Use Prettier with project defaults:
|
|
185
|
-
|
|
186
|
-
```json
|
|
187
|
-
{
|
|
188
|
-
"printWidth": 100,
|
|
189
|
-
"singleQuote": true,
|
|
190
|
-
"trailingComma": "all",
|
|
191
|
-
"tabWidth": 2
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Run `prettier --write .` before committing.
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## Git Commit Messages
|
|
200
|
-
|
|
201
|
-
Follow Conventional Commits:
|
|
202
|
-
|
|
203
|
-
```
|
|
204
|
-
feat(employees): add employee list page with pagination
|
|
205
|
-
fix(auth): redirect to login on 401 response
|
|
206
|
-
refactor(shared): extract confirm dialog to shared components
|
|
207
|
-
test(employees): add unit tests for EmployeeService
|
|
208
|
-
docs: update architecture standards
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## Code Review Checklist
|
|
214
|
-
|
|
215
|
-
- [ ] Standalone component with explicit `imports`
|
|
216
|
-
- [ ] OnPush change detection
|
|
217
|
-
- [ ] `inject()` instead of constructor DI
|
|
218
|
-
- [ ] Signals for local state
|
|
219
|
-
- [ ] Typed forms / typed HTTP
|
|
220
|
-
- [ ] No `any` types
|
|
221
|
-
- [ ] Tests for services and complex components
|
|
222
|
-
- [ ] No hardcoded URLs or secrets
|
|
223
|
-
- [ ] Accessibility: labels, ARIA, keyboard navigation
|