@tungvivas/angular-vibe-kit 0.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.
@@ -0,0 +1,69 @@
1
+ Create a new feature module. Follow these steps in exact order.
2
+
3
+ Angular idioms depend on the project version — read `.claude/angular-practices/`
4
+ and follow it. **Precedence (see `.claude/rules/project-rules.md` → Precedence):** a valid
5
+ project convention wins over the BP profile; the profile applies only where the project
6
+ has no convention or is below standard (new code only); legacy modules are never refactored.
7
+
8
+ ## Step 1: Understand Context
9
+ - Read `CLAUDE.md` → links to docs below
10
+ - Read `.claude/rules/project-rules.md` → naming, structure, Coexistence rules, **Reference Examples**
11
+ - Read `docs/ARCHITECTURE.md` → where this feature fits
12
+ - Read `docs/API_CONTRACT.md` → which endpoints this feature calls
13
+ - Read `docs/DESIGN_SYSTEM.md` → shared components to use
14
+ - **Open the Reference Example files** listed in `project-rules.md` (best existing service / page / component / spec) and mirror their style — this is the strongest signal for matching the project's real conventions
15
+ - Ask me if anything is unclear before writing code
16
+
17
+ ## Step 2: Create Feature Structure
18
+
19
+ > **Use the project's actual layout and folder names from `project-rules.md`.**
20
+ > The tree below is only the DEFAULT shape — if the project calls things differently
21
+ > (e.g. `containers/` instead of `pages/`, `ui/` or `common/` instead of `shared/`,
22
+ > an Nx `libs/` layout, or NgModule-based folders), follow the project, not this tree.
23
+
24
+ ```
25
+ {features-dir}/{feature-name}/ # e.g. features/, modules/, or an Nx lib
26
+ ├── {feature}.routes.ts
27
+ ├── models/
28
+ │ └── {feature}.model.ts # Interfaces mirroring the backend response shape
29
+ ├── services/
30
+ │ └── {feature}.service.ts # All data-access calls
31
+ ├── pages/ # Smart (container) components — project may call these containers/
32
+ │ ├── {feature}-list/
33
+ │ │ ├── {feature}-list.component.ts
34
+ │ │ ├── {feature}-list.component.html
35
+ │ │ └── {feature}-list.component.scss
36
+ │ └── {feature}-detail/
37
+ └── components/ # Dumb (presentational) components
38
+ └── {feature}-form/
39
+ ```
40
+
41
+ ## Step 3: Implement in This Order
42
+ 1. **Model** — interfaces for entity, request/response, mirroring the backend's actual DTOs / response shape (per `project-rules.md` — envelope, raw DTO, or GraphQL type)
43
+ 2. **Service** — data-access methods returning `Observable<T>` typed with the project's response shape (HttpClient, or the project's client if it uses Apollo/another)
44
+ 3. **Routes** — lazy-loaded, guard-protected where needed
45
+ 4. **Page components (smart)** — data, loading/error/empty states
46
+ 5. **Presentational components (dumb)** — inputs/outputs only, no data access
47
+
48
+ ## Step 4: Write Tests (see /write-tests)
49
+ - Service: mock HttpClient, test success + error + empty per method
50
+ - Component: mock service, test render + interaction + loading + error states
51
+
52
+ ## Step 5: Rules (from .claude/rules/project-rules.md + .claude/angular-practices/)
53
+ - Data access ONLY in services — never in components
54
+ - `ChangeDetectionStrategy.OnPush` on every component
55
+ - Tear down subscriptions per profile: `takeUntilDestroyed` (v16+) or `takeUntil(destroy$)` + `ngOnDestroy` (v12–15)
56
+ - No `any` — interfaces/types for every model and response
57
+ - Use the project's forms approach from `project-rules.md` (Reactive Forms by default; Formly / template-driven if that's what the project uses)
58
+ - No hardcoded backend URL — use `environment`
59
+
60
+ ## Step 6: Update Documentation
61
+ - `docs/API_CONTRACT.md` if you used/discovered endpoints
62
+ - Create `CONTEXT.md` inside the feature folder (use /write-context)
63
+ - `docs/PROJECT-STATUS.md` (use /update-status)
64
+
65
+ ## Step 7: Verify
66
+ - Build passes: `ng build` / `npx tsc --noEmit`
67
+ - All tests pass: `ng test` / `jest`
68
+ - Lint passes: `ng lint`
69
+ - Review with /review-pr before committing
@@ -0,0 +1,63 @@
1
+ Review the code changes in this PR. Check every item — flag violations clearly.
2
+
3
+ Read `.claude/angular-practices/` for version-correct idioms, and
4
+ `.claude/rules/project-rules.md` for project-specific rules.
5
+
6
+ **Precedence (see `project-rules.md` → Precedence):** a valid project convention wins over
7
+ the BP profile; flag a BP deviation as a blocker only when it's an objective defect in NEW
8
+ code. Do NOT flag legacy modules listed in the Coexistence Strategy — they are kept as-is.
9
+ Do NOT flag a valid project choice (naming, structure, state lib, response shape) just
10
+ because it differs from the profile.
11
+
12
+ ## Architecture
13
+ - [ ] Smart / dumb separation: pages own data; presentational components only render + emit
14
+ - [ ] HTTP calls ONLY in services — never in components
15
+ - [ ] No business logic in templates
16
+ - [ ] Feature routes are lazy-loaded
17
+
18
+ ## Type Safety
19
+ - [ ] No `any` — interfaces/types for every model and API response
20
+ - [ ] Responses typed with the project's response shape recorded in `project-rules.md` (e.g. an `ApiResponse<T>` envelope, a raw DTO, or a GraphQL type — whatever the backend actually returns)
21
+ - [ ] No `@ts-ignore` / `@ts-expect-error`
22
+
23
+ ## Dependency Injection (follow `.claude/angular-practices/`)
24
+ - [ ] DI matches the profile: `inject()` (v16+) or constructor injection (v12–15) — consistent across the project
25
+ - [ ] Services `@Injectable({ providedIn: 'root' })`
26
+
27
+ ## Reactive / RxJS / Signals (follow `.claude/angular-practices/`)
28
+ - [ ] Subscriptions torn down: `takeUntilDestroyed` (v16+) or `takeUntil(destroy$)` + `ngOnDestroy` (v12–15)
29
+ - [ ] `async` pipe preferred over manual subscribe
30
+ - [ ] No `toPromise()` — use `firstValueFrom()` if needed
31
+ - [ ] Services return `Observable<T>` — do NOT subscribe internally
32
+ - [ ] Signals used per profile: stable for state (v16+); derived state via `computed()`. `resource`/`httpResource` are experimental (v19+) — only if the team opted in
33
+
34
+ ## Error Handling
35
+ - [ ] Loading / error / empty states handled in list/detail pages
36
+ - [ ] HTTP errors surfaced to user (not swallowed)
37
+ - [ ] Auth errors (e.g. 401) handled centrally by an interceptor — not per-feature
38
+
39
+ ## Security
40
+ - [ ] Auth handled per the project's model in `project-rules.md` (token in memory / httpOnly cookie / session / OAuth) — and consistent with it. If tokens are used, they are NOT in localStorage
41
+ - [ ] No hardcoded backend URL — uses `environment`
42
+ - [ ] Routes needing auth are guarded
43
+ - [ ] No unsanitized HTML (`innerHTML` / `bypassSecurityTrust*`) without a clear, reviewed reason
44
+
45
+ ## Performance
46
+ - [ ] `ChangeDetectionStrategy.OnPush` on every component (or zoneless on v20+ if enabled)
47
+ - [ ] List tracking present: `@for` with `track` (v17+) or `*ngFor` with `trackBy` (v12–16)
48
+
49
+ ## Code Quality
50
+ - [ ] Component file < ~300 lines; method < ~50 lines
51
+ - [ ] No dead code, no `console.log`
52
+ - [ ] Naming follows `.claude/rules/project-rules.md`
53
+
54
+ ## Documentation
55
+ - [ ] New feature: `CONTEXT.md` created inside feature folder
56
+ - [ ] Logic changed: `CONTEXT.md` Refactor Log updated (append only)
57
+ - [ ] Endpoint changed: `docs/API_CONTRACT.md` updated
58
+ - [ ] `docs/PROJECT-STATUS.md` updated
59
+
60
+ ## Summary
61
+ 1. **Blockers** 🔴 — must fix before merge
62
+ 2. **Suggestions** 🟡 — improve but not blocking
63
+ 3. **Good parts** 🟢 — what was done well
@@ -0,0 +1,21 @@
1
+ Start of a new coding session. Read context before doing anything.
2
+
3
+ ## Read These (always)
4
+ - Read `CLAUDE.md` → project overview and links to docs
5
+ - Read `docs/PROJECT-STATUS.md` → current progress, warnings, next tasks
6
+
7
+ ## Read These (only when needed)
8
+ - `.claude/rules/project-rules.md` → ONLY if this is your first session OR you're about to write code
9
+ - `docs/ARCHITECTURE.md` → ONLY if task involves cross-module work or a new feature
10
+ - `docs/API_CONTRACT.md` → ONLY if task involves calling backend endpoints
11
+ - `docs/DESIGN_SYSTEM.md` → ONLY if task involves UI/components
12
+ - A feature's `CONTEXT.md` → ONLY if you're modifying that feature
13
+
14
+ ## After Reading, Tell Me
15
+ 1. What was last completed
16
+ 2. What is in progress (if any)
17
+ 3. Any active warnings
18
+ 4. Suggested next task
19
+
20
+ ## Then Wait
21
+ Do NOT start coding. Wait for me to confirm or change the task.
@@ -0,0 +1,19 @@
1
+ Update `docs/PROJECT-STATUS.md` based on what we did this session.
2
+
3
+ Rules:
4
+ 1. Read the current `docs/PROJECT-STATUS.md` first
5
+ 2. Move completed items from "In Progress" → "Completed" (with date)
6
+ 3. Add any new work done this session to "Completed"
7
+ 4. Update "In Progress" with anything still unfinished (note which component/file)
8
+ 5. Add discovered issues to "Deferred Issues" with priority [P1/P2/P3]
9
+ 6. Add gotchas to "Warnings"
10
+ 7. Update "Next Tasks" — reorder by current priority
11
+ 8. Update the header: date, session number (increment by 1)
12
+ 9. Keep the file under 80 lines — archive old "Completed" items if too long
13
+
14
+ After updating, commit code and docs together:
15
+ ```
16
+ git add CLAUDE.md docs/ .claude/rules/ src/
17
+ git commit -m "feat: {what was built} + tests + docs"
18
+ ```
19
+ Docs and code in the same commit — docs that lag behind code go stale.
@@ -0,0 +1,71 @@
1
+ Write a `CONTEXT.md` file for the current feature module.
2
+
3
+ This file is a **snapshot** — it captures WHY this module was built this way,
4
+ at the time it was built. It helps future developers (and AI) understand decisions
5
+ without guessing from code.
6
+
7
+ ## Where to Create
8
+ Place the file inside the feature folder, using the project's actual layout from
9
+ `project-rules.md` (the `features/` path below is only the default example):
10
+ ```
11
+ {features-dir}/{feature-name}/CONTEXT.md # e.g. features/, modules/, or an Nx lib
12
+ ```
13
+
14
+ ## Template — Fill Every Section
15
+
16
+ ```markdown
17
+ # {Feature Name} — Implementation Context
18
+ > Written: {YYYY-MM-DD} | Author: {who built it}
19
+
20
+ ## Business Context
21
+ Why does this module exist? What user/business problem does it solve?
22
+ (2-3 sentences max)
23
+
24
+ ## Components
25
+ - `{Feature}ListPage` — smart component: fetches data, pagination, handles loading/error/empty
26
+ - `{Feature}DetailPage` — smart component: create/edit form (per the project's forms approach), submit
27
+ - `{Feature}FormComponent` — dumb component: receives form/inputs, emits events
28
+ - `{Feature}CardComponent` — dumb component: renders one item
29
+
30
+ ## Service
31
+ `{Feature}Service` — all HTTP calls to `{base endpoint}`
32
+
33
+ ## State Management
34
+ Which approach and WHY (Signals / NgRx / BehaviorSubject / plain service).
35
+ Note what is local component state vs shared state.
36
+
37
+ ## API Endpoints Used
38
+ - GET {path}?page=&size= → paginated list
39
+ - GET {path}/:id → single
40
+ - POST {path} → create
41
+ - PUT {path}/:id → update
42
+ - DELETE {path}/:id → delete
43
+
44
+ ## Technical Decisions
45
+ - **{Decision}**: {reason}
46
+ - **{Decision}**: {reason}
47
+
48
+ ## Considered and Rejected
49
+ - **{Option}**: rejected because {reason}
50
+
51
+ ## Dependencies
52
+ - Depends on: {other modules/services this uses}
53
+ - Depended by: {modules that use this}
54
+
55
+ ## Known Limitations
56
+ - ⚠️ {limitation — explain why it's acceptable for now}
57
+
58
+ ## Refactor Log
59
+ (Add entries here when significant changes are made. Do NOT edit sections above.)
60
+
61
+ ### {YYYY-MM-DD} | {author}
62
+ - {what changed and why}
63
+ ```
64
+
65
+ ## Rules
66
+ 1. Write in clear language
67
+ 2. Be specific — "chose Signals because state is local to this page", not just "chose Signals"
68
+ 3. Keep each section concise (2-5 bullet points max)
69
+ 4. "Known Limitations" is critical — prevents future devs from "fixing" intentional trade-offs
70
+ 5. NEVER edit old content — only ADD new entries in the Refactor Log
71
+ 6. Write it NOW while context is fresh — tomorrow you'll forget half the reasons
@@ -0,0 +1,110 @@
1
+ Write tests for the specified feature module. Read the source code first, then generate tests.
2
+
3
+ Use the test runner the project already uses (Jest or Karma/Jasmine — check
4
+ `package.json` / `angular.json`). Read `.claude/angular-practices/` for the
5
+ version's testing idioms.
6
+
7
+ ## Step 1: Read Before Writing
8
+ - Read the feature's source: model, service, page components, presentational components
9
+ - Read the module's `CONTEXT.md` (if it exists) to understand trade-offs and edge cases
10
+ - Read `docs/API_CONTRACT.md` for expected request/response shapes
11
+ - Identify all code paths: happy path, error cases, empty states, validation
12
+
13
+ ## Step 2: Unit Tests — `{feature}.service.spec.ts`
14
+
15
+ Use `HttpClientTestingModule` + `HttpTestingController` (works on every version, v12+).
16
+ On v15+ you may instead use the functional `provideHttpClient()` + `provideHttpClientTesting()`.
17
+ Follow whichever style the project already uses (see `.claude/angular-practices/`).
18
+
19
+ ```typescript
20
+ describe('{Feature}Service', () => {
21
+ let service: {Feature}Service;
22
+ let httpMock: HttpTestingController;
23
+
24
+ beforeEach(() => {
25
+ TestBed.configureTestingModule({
26
+ imports: [HttpClientTestingModule],
27
+ providers: [{Feature}Service],
28
+ });
29
+ service = TestBed.inject({Feature}Service);
30
+ httpMock = TestBed.inject(HttpTestingController);
31
+ });
32
+
33
+ afterEach(() => httpMock.verify());
34
+ });
35
+ ```
36
+
37
+ ### Required Coverage Per Method
38
+ **getAll:**
39
+ - ✅ Success — returns list; assert request URL + method `GET`
40
+ - ✅ Empty — returns empty array, not null
41
+ - ✅ Server error — propagates error (500)
42
+
43
+ **getById:**
44
+ - ✅ Found — returns correct response; assert URL contains the id
45
+ - ✅ Not found — 404 error propagated
46
+
47
+ **create:**
48
+ - ✅ Success — assert `POST` + request body matches payload
49
+ - ✅ Validation error — 400
50
+ - ✅ Conflict — 409
51
+
52
+ **update:**
53
+ - ✅ Success — assert `PUT`/`PATCH` + body
54
+ - ✅ Not found — 404
55
+
56
+ **delete:**
57
+ - ✅ Success — assert `DELETE` + URL
58
+ - ✅ Not found — 404
59
+
60
+ ### Unit Test Rules
61
+ - Use `httpMock.expectOne(url)` to assert and flush each request
62
+ - Test ONE behavior per test
63
+ - Naming: `methodName_scenario_expectedResult`
64
+ - Describe behavior in the test title, not the implementation
65
+ - `httpMock.verify()` in `afterEach` — no unexpected requests
66
+ - Never hit a real backend
67
+
68
+ ## Step 3: Component Tests — `{feature}-list.component.spec.ts`
69
+
70
+ Mock the service with `jasmine.createSpyObj` (Karma) or `jest.fn()` (Jest).
71
+
72
+ ```typescript
73
+ describe('{Feature}ListComponent', () => {
74
+ let fixture: ComponentFixture<{Feature}ListComponent>;
75
+ let serviceSpy: jasmine.SpyObj<{Feature}Service>;
76
+
77
+ beforeEach(() => {
78
+ serviceSpy = jasmine.createSpyObj('{Feature}Service', ['getAll', 'delete']);
79
+ TestBed.configureTestingModule({
80
+ imports: [{Feature}ListComponent],
81
+ providers: [{ provide: {Feature}Service, useValue: serviceSpy }],
82
+ });
83
+ });
84
+ });
85
+ ```
86
+
87
+ ### Required Coverage
88
+ - ✅ Render — list shows correct data after load
89
+ - ✅ Loading — spinner visible while the request is pending
90
+ - ✅ Error — error message visible when the service errors
91
+ - ✅ Empty — empty-state message when no data
92
+ - ✅ Interaction — clicking delete calls `service.delete` with the right id
93
+ - ✅ Signals (v16+) — assert signal values update as expected
94
+
95
+ ### Component Test Rules
96
+ - Mock the service — never the real HttpClient
97
+ - Drive change detection with `fixture.detectChanges()`
98
+ - Query the DOM via `fixture.nativeElement` / `DebugElement`
99
+ - Each test is independent — no shared mutable state
100
+
101
+ ## Step 4: E2E (only for critical flows)
102
+ For login and the main CRUD flow, add a Playwright spec under the project's e2e folder.
103
+ Skip E2E for trivial pages — manual testing covers those.
104
+
105
+ ## Step 5: Verify
106
+ - All tests pass: `ng test` / `jest`
107
+ - No test depends on execution order
108
+ - No unit test hits a real backend
109
+ - Test names clearly describe WHAT is tested and the EXPECTED result
110
+ - Coverage: every public service method has at least happy path + error case
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@tungvivas/angular-vibe-kit",
3
+ "version": "0.1.0",
4
+ "description": "Bootstrap a Vibe Coding workflow (CLAUDE.md, project docs, slash-commands) into any Angular project. Detects your Angular version and installs version-matched best-practice rules.",
5
+ "keywords": [
6
+ "angular",
7
+ "claude",
8
+ "claude-code",
9
+ "ai",
10
+ "scaffold",
11
+ "vibe-coding",
12
+ "slash-commands"
13
+ ],
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "bin": {
17
+ "angular-vibe-kit": "bin/cli.js"
18
+ },
19
+ "files": [
20
+ "bin/",
21
+ "commands/",
22
+ "practices/",
23
+ "templates/",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=16"
29
+ },
30
+ "scripts": {
31
+ "test": "node bin/cli.js --help",
32
+ "prepublishOnly": "node -c bin/cli.js"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/vuanhtung10/angular-vibe-kit.git"
37
+ },
38
+ "homepage": "https://github.com/vuanhtung10/angular-vibe-kit#readme",
39
+ "bugs": "https://github.com/vuanhtung10/angular-vibe-kit/issues"
40
+ }
@@ -0,0 +1,193 @@
1
+ # Angular Best Practices — v12 / v13 (NgModule + NgRx Era)
2
+
3
+ > Framework-level patterns only. This file describes HOW to write idiomatic
4
+ > Angular 12/13 — syntax, DI, components, RxJS. It does NOT dictate folder
5
+ > structure, state-management choice, or naming — those are project decisions
6
+ > recorded in `.claude/rules/project-rules.md` (inferred from the codebase).
7
+ >
8
+ > When the project already follows a correct pattern, follow the project.
9
+ > When it is below standard, apply this standard to NEW code only.
10
+
11
+ ---
12
+
13
+ ## Module System
14
+ - **NgModule is the only option.** Standalone components do NOT exist in v12/13.
15
+ - Every feature has a `*.module.ts` + `*-routing.module.ts`.
16
+ - Feature modules are lazy-loaded via `loadChildren`.
17
+ - `CoreModule` (imported once in AppModule) owns singleton services, interceptors, guards.
18
+ - `SharedModule` owns reusable components, pipes, directives — exported for feature modules.
19
+
20
+ ```typescript
21
+ @NgModule({
22
+ declarations: [UserListComponent, UserCardComponent],
23
+ imports: [CommonModule, UserRoutingModule, ReactiveFormsModule, SharedModule],
24
+ providers: [],
25
+ })
26
+ export class UserModule {}
27
+ ```
28
+
29
+ ## Component Pattern
30
+ - `changeDetection: ChangeDetectionStrategy.OnPush` on every component.
31
+ - **Smart (container) vs Dumb (presentational)** separation — mandatory.
32
+ - Smart: fetches data, owns state, dispatches NgRx actions or calls services.
33
+ - Dumb: receives data via `@Input()`, emits via `@Output()`, zero HTTP/store calls.
34
+ - Inputs/outputs with `@Input()` / `@Output()`.
35
+
36
+ ```typescript
37
+ @Component({
38
+ selector: 'app-user-list',
39
+ templateUrl: './user-list.component.html',
40
+ changeDetection: ChangeDetectionStrategy.OnPush,
41
+ })
42
+ export class UserListComponent implements OnInit, OnDestroy {
43
+ users: User[] = [];
44
+ isLoading = false;
45
+ error: string | null = null;
46
+ private destroy$ = new Subject<void>();
47
+
48
+ constructor(private userService: UserService) {}
49
+
50
+ ngOnInit(): void { this.loadUsers(); }
51
+ ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
52
+ }
53
+ ```
54
+
55
+ ## Template Syntax — Structural Directives
56
+ - Use `*ngIf`, `*ngFor` (with `trackBy`), `[ngSwitch]`.
57
+ - Built-in `@if/@for` does NOT exist (v17+ only).
58
+ - Always use `trackBy` with `*ngFor`.
59
+
60
+ ```html
61
+ <app-spinner *ngIf="isLoading"></app-spinner>
62
+ <p *ngIf="error" class="error">{{ error }}</p>
63
+ <app-user-card
64
+ *ngFor="let user of users; trackBy: trackById"
65
+ [user]="user"
66
+ (delete)="onDelete($event)">
67
+ </app-user-card>
68
+ <p *ngIf="!isLoading && users.length === 0">No data.</p>
69
+ ```
70
+
71
+ ## Services & Dependency Injection
72
+ - **Constructor injection only.** `inject()` function does NOT exist in v12.
73
+ - `@Injectable({ providedIn: 'root' })` for singleton services.
74
+ - HttpClient lives ONLY in services — never call HTTP from a component.
75
+ - Services return `Observable<T>` and NEVER subscribe internally.
76
+
77
+ ```typescript
78
+ @Injectable({ providedIn: 'root' })
79
+ export class UserService {
80
+ private baseUrl = `${environment.apiUrl}/users`;
81
+ constructor(private http: HttpClient) {}
82
+
83
+ getAll(params?: UserFilterParams): Observable<ApiResponse<User[]>> {
84
+ return this.http.get<ApiResponse<User[]>>(this.baseUrl, { params: { ...params } });
85
+ }
86
+ getById(id: number): Observable<ApiResponse<User>> {
87
+ return this.http.get<ApiResponse<User>>(`${this.baseUrl}/${id}`);
88
+ }
89
+ create(payload: CreateUserRequest): Observable<ApiResponse<User>> {
90
+ return this.http.post<ApiResponse<User>>(this.baseUrl, payload);
91
+ }
92
+ update(id: number, payload: UpdateUserRequest): Observable<ApiResponse<User>> {
93
+ return this.http.put<ApiResponse<User>>(`${this.baseUrl}/${id}`, payload);
94
+ }
95
+ delete(id: number): Observable<ApiResponse<void>> {
96
+ return this.http.delete<ApiResponse<void>>(`${this.baseUrl}/${id}`);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## HTTP
102
+ - `HttpClientModule` imported in `CoreModule` / `AppModule`.
103
+ - Class-based interceptors registered via `HTTP_INTERCEPTORS` multi-provider in `CoreModule`.
104
+ - Auth interceptor attaches `Authorization: Bearer <token>`.
105
+ - Error interceptor handles 401 (refresh/redirect) and maps errors to user messages.
106
+ - Avoid `toPromise()` (deprecated) — use `firstValueFrom()` / `lastValueFrom()`.
107
+
108
+ ```typescript
109
+ @Injectable()
110
+ export class AuthInterceptor implements HttpInterceptor {
111
+ constructor(private auth: AuthService) {}
112
+ intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
113
+ const token = this.auth.accessToken;
114
+ return token
115
+ ? next.handle(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }))
116
+ : next.handle(req);
117
+ }
118
+ }
119
+ ```
120
+
121
+ ## State Management — NgRx (common in v12 projects)
122
+ - If the project uses NgRx: Actions → Reducer → Effects → Selectors pattern.
123
+ - Smart components dispatch actions and select from store — NEVER call service directly when NgRx manages that slice.
124
+ - Effects own all side effects (HTTP calls, navigation).
125
+ - Selectors are the ONLY way to read from the store in components.
126
+
127
+ ```typescript
128
+ // Smart component with NgRx
129
+ export class UserListComponent implements OnInit, OnDestroy {
130
+ users$ = this.store.select(selectUsers);
131
+ isLoading$ = this.store.select(selectUsersLoading);
132
+ private destroy$ = new Subject<void>();
133
+
134
+ constructor(private store: Store) {}
135
+ ngOnInit(): void { this.store.dispatch(UserActions.loadUsers()); }
136
+ ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
137
+ onDelete(id: number): void { this.store.dispatch(UserActions.deleteUser({ id })); }
138
+ }
139
+ ```
140
+
141
+ - If the project does NOT use NgRx: `BehaviorSubject` in a service for shared state, local state in component fields.
142
+
143
+ ## Routing
144
+ - Lazy-load feature modules with `loadChildren: () => import('...').then(m => m.UserModule)`.
145
+ - Class-based guards implementing `CanActivate`, `CanLoad`.
146
+
147
+ ## Reactive / RxJS (v6.x)
148
+ - No Signals (v16+ only). State is component fields or `BehaviorSubject`.
149
+ - Tear down ALL subscriptions with `takeUntil(this.destroy$)` + `ngOnDestroy`.
150
+ - Prefer the `async` pipe in templates over manual `subscribe` where possible.
151
+
152
+ ```typescript
153
+ private loadUsers(): void {
154
+ this.isLoading = true;
155
+ this.userService.getAll()
156
+ .pipe(takeUntil(this.destroy$))
157
+ .subscribe({
158
+ next: (res) => { this.users = res.data; this.isLoading = false; },
159
+ error: () => { this.error = 'Could not load users'; this.isLoading = false; },
160
+ });
161
+ }
162
+ ```
163
+
164
+ ## Forms
165
+ - Reactive Forms (`FormBuilder`) for all forms with validation — never Template-driven for complex forms.
166
+ - Show validation messages when a control is `touched` and `invalid`.
167
+ - Disable submit button while request is pending.
168
+
169
+ ## TypeScript
170
+ - No `any` — use proper `interface` / `type`.
171
+ - Mirror backend DTOs in `*.model.ts`.
172
+ - `readonly` for arrays/objects that must not be mutated.
173
+
174
+ ## Testing
175
+ - Unit (service): `HttpClientTestingModule` + `HttpTestingController`.
176
+ - Component: `TestBed.configureTestingModule` + mocked service (`jasmine.createSpyObj`).
177
+ - E2E: Protractor (default in v12, EOL) or Cypress/Playwright if the project migrated.
178
+ - Runner: Karma + Jasmine (default in v12).
179
+
180
+ ## Security
181
+ - Tokens in memory (service field) or httpOnly cookie — NOT localStorage.
182
+ - No hardcoded backend URL — use `environment.apiUrl`.
183
+ - Guard routes with `CanActivate`; use `CanLoad` to prevent lazy module download.
184
+
185
+ ---
186
+
187
+ ## Key Differences vs v14-15
188
+ - No standalone components — NgModule only.
189
+ - No `inject()` function — constructor injection only.
190
+ - No `takeUntilDestroyed` — use the `Subject + takeUntil + ngOnDestroy` pattern.
191
+ - No required `@Input()` decorator — use `!` assertion or explicit check.
192
+ - RxJS 6.x (pipeable operators, `of`, `from`, etc. — same as later versions).
193
+ - NgRx v12 — `createAction`, `createReducer`, `createEffect` patterns are standard.