@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,133 @@
1
+ # Angular Best Practices — v14 / v15 (NgModule Era)
2
+
3
+ > Framework-level patterns only. This file describes HOW to write idiomatic
4
+ > Angular 14/15 — syntax, DI, components, RxJS. It does NOT dictate folder
5
+ > structure, state-management choice, or naming — those are project decisions
6
+ > recorded in `docs/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 norm.** Each feature has a `*.module.ts` and a `*-routing.module.ts`.
15
+ - Standalone components exist (v14 preview, v15 stable) but the ecosystem and most projects are module-based — do not force standalone unless the project already uses it.
16
+ - Feature modules are lazy-loaded via `loadChildren`.
17
+
18
+ ```typescript
19
+ @NgModule({
20
+ declarations: [UserListComponent, UserCardComponent],
21
+ imports: [CommonModule, UserRoutingModule, ReactiveFormsModule],
22
+ })
23
+ export class UserModule {}
24
+ ```
25
+
26
+ ## Component Pattern
27
+ - `changeDetection: ChangeDetectionStrategy.OnPush` on every component.
28
+ - Smart (container) vs dumb (presentational) split.
29
+ - Inputs/outputs with `@Input()` / `@Output()`.
30
+
31
+ ```typescript
32
+ @Component({
33
+ selector: 'app-user-list',
34
+ templateUrl: './user-list.component.html',
35
+ changeDetection: ChangeDetectionStrategy.OnPush,
36
+ })
37
+ export class UserListComponent implements OnInit, OnDestroy {
38
+ users: User[] = [];
39
+ isLoading = false;
40
+ error: string | null = null;
41
+ private destroy$ = new Subject<void>();
42
+
43
+ constructor(private userService: UserService) {}
44
+
45
+ ngOnInit(): void { this.loadUsers(); }
46
+ ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
47
+ }
48
+ ```
49
+
50
+ ## Template Syntax — Structural Directives
51
+ - Use `*ngIf`, `*ngFor`, `[ngSwitch]`. (Built-in `@if/@for` does NOT exist yet.)
52
+ - Always use `trackBy` with `*ngFor` for lists.
53
+
54
+ ```html
55
+ <app-spinner *ngIf="isLoading"></app-spinner>
56
+ <div *ngIf="error as msg" class="error">{{ msg }}</div>
57
+ <app-user-card
58
+ *ngFor="let user of users; trackBy: trackById"
59
+ [user]="user"
60
+ (delete)="onDelete($event)">
61
+ </app-user-card>
62
+ ```
63
+
64
+ ## Services & Dependency Injection
65
+ - **Constructor injection** — `inject()` is not yet idiomatic here.
66
+ - `@Injectable({ providedIn: 'root' })` for singletons.
67
+ - HttpClient lives ONLY in services. Services return `Observable<T>`, never subscribe internally.
68
+
69
+ ```typescript
70
+ @Injectable({ providedIn: 'root' })
71
+ export class UserService {
72
+ private baseUrl = `${environment.apiUrl}/users`;
73
+ constructor(private http: HttpClient) {}
74
+
75
+ getAll(params?: UserFilterParams): Observable<ApiResponse<User[]>> {
76
+ return this.http.get<ApiResponse<User[]>>(this.baseUrl, { params: { ...params } });
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## HTTP
82
+ - `HttpClientModule` imported in `CoreModule` / `AppModule`.
83
+ - Class-based interceptors registered with `HTTP_INTERCEPTORS` multi-provider.
84
+ - Auth interceptor attaches `Authorization`; error interceptor maps errors.
85
+
86
+ ```typescript
87
+ @Injectable()
88
+ export class AuthInterceptor implements HttpInterceptor {
89
+ constructor(private auth: AuthService) {}
90
+ intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
91
+ const token = this.auth.accessToken;
92
+ return token
93
+ ? next.handle(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }))
94
+ : next.handle(req);
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## Routing
100
+ - Lazy load feature modules with `loadChildren: () => import('...').then(m => m.UserModule)`.
101
+ - Class-based guards implementing `CanActivate`.
102
+
103
+ ## Reactive / RxJS
104
+ - No Signals. State is plain class fields + RxJS (`BehaviorSubject` for shared state).
105
+ - Tear down subscriptions with `takeUntil(this.destroy$)` and the OnDestroy pattern.
106
+ - Prefer the `async` pipe in templates over manual subscribe.
107
+ - Avoid `toPromise()` (deprecated) — use `firstValueFrom()` / `lastValueFrom()`.
108
+
109
+ ## Forms
110
+ - Reactive Forms (`FormBuilder`) for validated forms. Template-driven only for trivial cases.
111
+ - Show validation messages when control is `touched` and invalid.
112
+
113
+ ## TypeScript
114
+ - No `any`. Interfaces/types for models. Mirror backend DTOs in `*.model.ts`.
115
+
116
+ ## Testing
117
+ - Unit (service): `HttpClientTestingModule` + `HttpTestingController`.
118
+ - Component: `TestBed.configureTestingModule` + mocked service.
119
+ - E2E: Protractor is legacy/EOL — prefer Cypress or Playwright.
120
+ - Runner: Karma/Jasmine by default; Jest if the project added it.
121
+
122
+ ## Security
123
+ - Tokens in memory or httpOnly cookie — NOT localStorage.
124
+ - No hardcoded API URL — use `environment`.
125
+
126
+ ---
127
+
128
+ ## Notes vs Newer Versions
129
+ - No `@if/@for` control flow — use `*ngIf/*ngFor`.
130
+ - No `inject()` idiom — use constructor injection.
131
+ - No Signals — use RxJS + `BehaviorSubject`.
132
+ - No `@defer` — lazy-load at the route/module level instead.
133
+ - No `takeUntilDestroyed` — use the `Subject` + `takeUntil` + `ngOnDestroy` pattern.
@@ -0,0 +1,127 @@
1
+ # Angular Best Practices — v16 (Signals Preview Era)
2
+
3
+ > Framework-level patterns only. This file describes HOW to write idiomatic
4
+ > Angular 16 — syntax, DI, components, RxJS/Signals. It does NOT dictate folder
5
+ > structure, state-management choice, or naming — those are project decisions
6
+ > recorded in `docs/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
+ - **Transitional release.** Standalone is stable and recommended for new code, but many v16 projects are still NgModule-based.
15
+ - If the project is standalone → use `bootstrapApplication` + standalone components.
16
+ - If the project is module-based → keep adding to modules; introduce standalone only where it does not conflict.
17
+
18
+ ## Component Pattern
19
+ - `changeDetection: ChangeDetectionStrategy.OnPush` on every component.
20
+ - Smart vs dumb split.
21
+ - **Required inputs** available: `@Input({ required: true }) user!: User;`.
22
+ - `DestroyRef` + `takeUntilDestroyed()` introduced — use them for teardown.
23
+
24
+ ```typescript
25
+ @Component({
26
+ selector: 'app-user-list',
27
+ standalone: true,
28
+ imports: [CommonModule, UserCardComponent],
29
+ templateUrl: './user-list.component.html',
30
+ changeDetection: ChangeDetectionStrategy.OnPush,
31
+ })
32
+ export class UserListComponent {
33
+ private userService = inject(UserService);
34
+ private destroyRef = inject(DestroyRef);
35
+
36
+ // Signals are in developer preview in v16 — fine to use for local state.
37
+ users = signal<User[]>([]);
38
+ isLoading = signal(false);
39
+ }
40
+ ```
41
+
42
+ ## Template Syntax — Structural Directives
43
+ - Use `*ngIf`, `*ngFor` (+ `trackBy`), `[ngSwitch]`. The `@if/@for` built-in control flow does NOT exist yet (that is v17).
44
+
45
+ ```html
46
+ <app-spinner *ngIf="isLoading()"></app-spinner>
47
+ <app-user-card
48
+ *ngFor="let user of users(); trackBy: trackById"
49
+ [user]="user"
50
+ (delete)="onDelete($event)">
51
+ </app-user-card>
52
+ ```
53
+
54
+ ## Services & Dependency Injection
55
+ - **`inject()` function is idiomatic in v16** — prefer it over constructor injection for new code.
56
+ - `@Injectable({ providedIn: 'root' })` for singletons.
57
+ - HttpClient only in services; services return `Observable<T>` and never subscribe.
58
+
59
+ ```typescript
60
+ @Injectable({ providedIn: 'root' })
61
+ export class UserService {
62
+ private http = inject(HttpClient);
63
+ private baseUrl = `${environment.apiUrl}/users`;
64
+
65
+ getAll(params?: UserFilterParams): Observable<ApiResponse<User[]>> {
66
+ return this.http.get<ApiResponse<User[]>>(this.baseUrl, { params: { ...params } });
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## HTTP
72
+ - `provideHttpClient(withInterceptors([...]))` available for standalone apps (functional interceptors).
73
+ - Module-based apps still use `HttpClientModule` + class interceptors via `HTTP_INTERCEPTORS`.
74
+ - Avoid `toPromise()` — use `firstValueFrom()`.
75
+
76
+ ## Routing
77
+ - Lazy load with `loadComponent()` (standalone) or `loadChildren()` (modules).
78
+ - Functional guards (`CanActivateFn`) are available and preferred for new code.
79
+
80
+ ## Reactive / RxJS / Signals
81
+ - **Signals are developer preview** — safe for local component state; teams may still prefer RxJS for shared state.
82
+ - `computed()` for derived state. `effect()` for side effects.
83
+ - Subscriptions: `takeUntilDestroyed(this.destroyRef)`.
84
+ - Prefer the `async` pipe over manual subscribe.
85
+
86
+ ```typescript
87
+ private loadUsers(): void {
88
+ this.isLoading.set(true);
89
+ this.userService.getAll()
90
+ .pipe(takeUntilDestroyed(this.destroyRef))
91
+ .subscribe({
92
+ next: (res) => { this.users.set(res.data); this.isLoading.set(false); },
93
+ error: () => { this.isLoading.set(false); },
94
+ });
95
+ }
96
+ ```
97
+
98
+ ## Forms
99
+ - Reactive Forms (`FormBuilder` / `NonNullableFormBuilder`) for validated forms.
100
+ - Show validation messages when control is `touched` and invalid.
101
+
102
+ ## TypeScript
103
+ - No `any`. Interfaces/types for models, mirror backend DTOs in `*.model.ts`.
104
+
105
+ ## Testing
106
+ - Unit (service): `HttpClientTestingModule` + `HttpTestingController`.
107
+ - Component: `TestBed` + mocked service.
108
+ - E2E: Cypress or Playwright.
109
+ - Runner: Karma/Jasmine by default; Jest if the project added it.
110
+
111
+ ## Security
112
+ - Tokens in memory or httpOnly cookie — NOT localStorage.
113
+ - No hardcoded API URL — use `environment`.
114
+
115
+ ---
116
+
117
+ ## Breaking Changes / Notes vs v14-15
118
+ - **`inject()`** is now idiomatic (over constructor injection).
119
+ - **`DestroyRef` + `takeUntilDestroyed()`** replace the `Subject + takeUntil + ngOnDestroy` boilerplate.
120
+ - **Required inputs** `@Input({ required: true })`.
121
+ - **Signals** available (developer preview) for local state.
122
+ - **Functional interceptors/guards** available for standalone apps.
123
+
124
+ ## Notes vs v17
125
+ - No `@if/@for/@switch` built-in control flow yet — still `*ngIf/*ngFor`.
126
+ - No `@defer` blocks.
127
+ - Standalone not yet the CLI default.
@@ -0,0 +1,166 @@
1
+ # Angular Best Practices — v17 (Modern Era)
2
+
3
+ > Framework-level patterns only. This file describes HOW to write idiomatic
4
+ > Angular 17 — syntax, DI, components, RxJS. It does NOT dictate folder
5
+ > structure, state-management choice, or naming — those are project decisions
6
+ > recorded in `docs/PROJECT-RULES.md` (inferred from the codebase).
7
+ >
8
+ > Read this together with the project's own rules. When the project already
9
+ > follows a correct pattern, follow the project. When the project is below
10
+ > standard, apply this standard to NEW code only (see Coexistence in PROJECT-RULES.md).
11
+
12
+ ---
13
+
14
+ ## Module System
15
+ - **Standalone components by default** — `standalone: true`. No NgModule for new code.
16
+ - Bootstrap via `bootstrapApplication(AppComponent, appConfig)` in `main.ts`.
17
+ - Providers configured in `app.config.ts` (`provideRouter`, `provideHttpClient`, etc.).
18
+ - NgModule still supported — if the project is module-based, do not migrate; add new features as standalone where it does not conflict.
19
+
20
+ ## Component Pattern
21
+ - `changeDetection: ChangeDetectionStrategy.OnPush` on every component.
22
+ - Smart (page/container) components own data + side effects; presentational (dumb) components only render and emit.
23
+ - Keep templates in separate `.html` files for non-trivial components.
24
+
25
+ ```typescript
26
+ @Component({
27
+ selector: 'app-user-list',
28
+ standalone: true,
29
+ imports: [UserCardComponent, MatPaginatorModule],
30
+ templateUrl: './user-list.component.html',
31
+ changeDetection: ChangeDetectionStrategy.OnPush,
32
+ })
33
+ export class UserListComponent {
34
+ private userService = inject(UserService);
35
+ private destroyRef = inject(DestroyRef);
36
+
37
+ users = signal<User[]>([]);
38
+ isLoading = signal(false);
39
+ error = signal<string | null>(null);
40
+ }
41
+ ```
42
+
43
+ ## Template Syntax — Built-in Control Flow (new in v17)
44
+ - Use `@if`, `@else`, `@for`, `@switch` — the new built-in control flow.
45
+ - `@for` **requires** `track`: `@for (user of users(); track user.id) { ... }`.
46
+ - Prefer `@defer` for heavy/below-the-fold blocks.
47
+
48
+ ```html
49
+ @if (isLoading()) {
50
+ <app-spinner />
51
+ } @else if (error()) {
52
+ <app-error [message]="error()!" />
53
+ } @else {
54
+ @for (user of users(); track user.id) {
55
+ <app-user-card [user]="user" (delete)="onDelete($event)" />
56
+ } @empty {
57
+ <app-empty-state message="No users yet" />
58
+ }
59
+ }
60
+ ```
61
+
62
+ - `*ngIf` / `*ngFor` still work but prefer the new syntax for new code. Do not mass-migrate legacy templates.
63
+
64
+ ## Services & Dependency Injection
65
+ - Use the `inject()` function instead of constructor injection for new code.
66
+ - `@Injectable({ providedIn: 'root' })` for singleton services.
67
+ - HttpClient lives ONLY in services — never call HTTP from a component.
68
+ - Services return `Observable<T>` — never subscribe inside a service.
69
+
70
+ ```typescript
71
+ @Injectable({ providedIn: 'root' })
72
+ export class UserService {
73
+ private http = inject(HttpClient);
74
+ private baseUrl = `${environment.apiUrl}/users`;
75
+
76
+ getAll(params?: UserFilterParams): Observable<ApiResponse<User[]>> {
77
+ return this.http.get<ApiResponse<User[]>>(this.baseUrl, { params: { ...params } });
78
+ }
79
+ getById(id: number): Observable<ApiResponse<User>> {
80
+ return this.http.get<ApiResponse<User>>(`${this.baseUrl}/${id}`);
81
+ }
82
+ create(payload: CreateUserRequest): Observable<ApiResponse<User>> {
83
+ return this.http.post<ApiResponse<User>>(this.baseUrl, payload);
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## HTTP
89
+ - Configure with `provideHttpClient(withInterceptors([...]))` — functional interceptors.
90
+ - Auth: a functional interceptor attaches the `Authorization` header.
91
+ - Errors: an error interceptor maps HTTP errors to user-facing messages; components show them.
92
+ - Never use `toPromise()` (removed) — use `firstValueFrom()` if a promise is truly needed.
93
+
94
+ ```typescript
95
+ export const authInterceptor: HttpInterceptorFn = (req, next) => {
96
+ const token = inject(AuthService).accessToken();
97
+ return token
98
+ ? next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }))
99
+ : next(req);
100
+ };
101
+ ```
102
+
103
+ ## Routing
104
+ - Lazy-load every feature route with `loadComponent()` / `loadChildren()`.
105
+ - Functional guards: `CanActivateFn`. Functional resolvers: `ResolveFn`.
106
+
107
+ ```typescript
108
+ export const routes: Routes = [
109
+ { path: 'login', loadComponent: () => import('./features/auth/pages/login/login.component').then(m => m.LoginComponent) },
110
+ {
111
+ path: '',
112
+ canActivate: [authGuard],
113
+ children: [
114
+ { path: 'users', loadComponent: () => import('./features/user/pages/user-list/user-list.component').then(m => m.UserListComponent) },
115
+ ],
116
+ },
117
+ ];
118
+ ```
119
+
120
+ ## Reactive / RxJS / Signals
121
+ - **Signals** for component state (`signal`, `computed`, `effect`). `computed()` for derived state — never duplicate state.
122
+ - For subscriptions, always tear down with `takeUntilDestroyed(destroyRef)`.
123
+ - Prefer the `async` pipe in templates over manual `subscribe`.
124
+ - `toSignal()` / `toObservable()` bridge RxJS and signals when needed.
125
+
126
+ ```typescript
127
+ private loadUsers(): void {
128
+ this.isLoading.set(true);
129
+ this.userService.getAll()
130
+ .pipe(takeUntilDestroyed(this.destroyRef))
131
+ .subscribe({
132
+ next: (res) => { this.users.set(res.data); this.isLoading.set(false); },
133
+ error: () => { this.error.set('Could not load users'); this.isLoading.set(false); },
134
+ });
135
+ }
136
+ ```
137
+
138
+ ## Forms
139
+ - Reactive Forms (`FormBuilder` / `NonNullableFormBuilder`) for anything with validation.
140
+ - Template-driven only for 1–2 trivial fields.
141
+ - Show validation messages when a control is `touched` and invalid.
142
+
143
+ ## TypeScript
144
+ - No `any`. Use `interface`/`type`, `unknown` + narrowing for untrusted input.
145
+ - Mirror backend DTOs in `*.model.ts` (e.g. `ApiResponse<T>`, `PaginationResponse<T>`).
146
+ - `readonly` for inputs that never change.
147
+
148
+ ## Testing
149
+ - Unit (service): `HttpClientTestingModule` + `HttpTestingController`. Test success + error + empty per method.
150
+ - Component: `TestBed` + mocked service (`jasmine.createSpyObj` or `jest.fn()`). Test render, interaction, loading, error states.
151
+ - E2E: Playwright for critical flows (login, CRUD).
152
+ - Runner: Jest or Karma/Jasmine — follow whatever the project already uses.
153
+
154
+ ## Security
155
+ - Tokens in memory or httpOnly cookie — NOT localStorage.
156
+ - Never hardcode the API URL — use `environment`.
157
+ - Guard routes that require auth.
158
+
159
+ ---
160
+
161
+ ## Breaking Changes vs v16
162
+ - **Built-in control flow** `@if/@for/@switch` introduced (stable). `@for` requires `track`.
163
+ - **`@defer`** blocks introduced for lazy view loading.
164
+ - Standalone is the default in the CLI scaffolding.
165
+ - Vite + esbuild is the default dev/build pipeline.
166
+ - `toPromise()` fully removed — use `firstValueFrom()`.
@@ -0,0 +1,121 @@
1
+ # Angular Best Practices — v18 / v19 (Signal-First Era)
2
+
3
+ > Framework-level patterns only. This file describes HOW to write idiomatic
4
+ > Angular 18/19 — syntax, DI, components, Signals. It does NOT dictate folder
5
+ > structure, state-management choice, or naming — those are project decisions
6
+ > recorded in `docs/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
+ - **Standalone everywhere.** In v19 standalone is the implicit default (`standalone: true` no longer needs to be written, though being explicit is fine).
15
+ - Bootstrap with `bootstrapApplication(AppComponent, appConfig)`.
16
+ - Providers in `app.config.ts`. Do not add NgModules for new code.
17
+
18
+ ## Component Pattern
19
+ - `ChangeDetectionStrategy.OnPush` everywhere (or run zoneless — see below).
20
+ - **Signal-based inputs/outputs** are idiomatic:
21
+ - `input()` / `input.required()` instead of `@Input()`.
22
+ - `output()` instead of `@Output()`.
23
+ - `model()` for two-way binding.
24
+ - `viewChild()` / `contentChild()` signal queries.
25
+
26
+ ```typescript
27
+ @Component({
28
+ selector: 'app-user-card',
29
+ imports: [],
30
+ templateUrl: './user-card.component.html',
31
+ changeDetection: ChangeDetectionStrategy.OnPush,
32
+ })
33
+ export class UserCardComponent {
34
+ user = input.required<User>();
35
+ delete = output<number>();
36
+ onDeleteClick(): void { this.delete.emit(this.user().id); }
37
+ }
38
+ ```
39
+
40
+ ## Template Syntax — Built-in Control Flow
41
+ - `@if`, `@else`, `@for` (with required `track`), `@switch`, `@defer`, `@empty`, `@placeholder`.
42
+ - These are the default and `*ngIf/*ngFor` are effectively legacy — use the block syntax for all new templates.
43
+
44
+ ```html
45
+ @if (isLoading()) {
46
+ <app-spinner />
47
+ } @else {
48
+ @for (user of users(); track user.id) {
49
+ <app-user-card [user]="user" (delete)="onDelete($event)" />
50
+ } @empty {
51
+ <app-empty-state message="No users yet" />
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Services & Dependency Injection
57
+ - `inject()` everywhere. Constructor injection is legacy.
58
+ - `@Injectable({ providedIn: 'root' })` for singletons.
59
+ - HttpClient only in services; services return `Observable<T>` and never subscribe.
60
+
61
+ ## HTTP
62
+ - `provideHttpClient(withInterceptors([...]))` with functional interceptors.
63
+ - `httpResource()` (v19+, experimental) for declarative resource fetching where it fits — otherwise plain HttpClient.
64
+ - Never use `toPromise()` — use `firstValueFrom()`.
65
+
66
+ ```typescript
67
+ export const authInterceptor: HttpInterceptorFn = (req, next) => {
68
+ const token = inject(AuthService).accessToken();
69
+ return token
70
+ ? next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }))
71
+ : next(req);
72
+ };
73
+ ```
74
+
75
+ ## Routing
76
+ - Lazy-load with `loadComponent()` / `loadChildren()`.
77
+ - Functional guards (`CanActivateFn`) and resolvers (`ResolveFn`).
78
+
79
+ ## Reactive / Signals (signal-first)
80
+ - **Signals are the primary state primitive.** `signal`, `computed`, `effect`, `linkedSignal` (v19), `resource()` / `rxResource()` (v19, experimental).
81
+ - Bridge with `toSignal()` / `toObservable()`.
82
+ - Subscriptions (when still needed): `takeUntilDestroyed(destroyRef)`.
83
+ - Prefer the `async` pipe / signal reads in templates over manual subscribe.
84
+ - **Zoneless** change detection is available (experimental via `provideExperimentalZonelessChangeDetection()`). If the project enables it, do NOT rely on `setTimeout`/zone-triggered CD — drive updates through signals.
85
+
86
+ ```typescript
87
+ export class UserListComponent {
88
+ private userService = inject(UserService);
89
+ private destroyRef = inject(DestroyRef);
90
+
91
+ users = signal<User[]>([]);
92
+ isLoading = signal(false);
93
+ userCount = computed(() => this.users().length);
94
+ }
95
+ ```
96
+
97
+ ## Forms
98
+ - Reactive Forms (`NonNullableFormBuilder`) for validated forms.
99
+ - Show validation messages when a control is `touched` and invalid.
100
+
101
+ ## TypeScript
102
+ - No `any`. Interfaces/types for models, mirror backend DTOs in `*.model.ts`. `readonly` where applicable.
103
+
104
+ ## Testing
105
+ - Unit (service): `HttpClientTestingModule` + `HttpTestingController` (or `provideHttpClientTesting()`).
106
+ - Component: `TestBed` + mocked service; assert signal values and rendered output.
107
+ - E2E: Playwright.
108
+ - Runner: Jest or Karma/Jasmine — follow the project. (Web Test Runner / Vitest also appear in newer setups.)
109
+
110
+ ## Security
111
+ - Tokens in memory or httpOnly cookie — NOT localStorage.
112
+ - No hardcoded API URL — use `environment`.
113
+
114
+ ---
115
+
116
+ ## Breaking Changes / Notes vs v17
117
+ - **Signal inputs/outputs**: `input()`, `input.required()`, `output()`, `model()`, signal queries (`viewChild()`/`contentChild()`). Developer preview in v17.1–v18; **production-ready as of v19**. Preferred over decorators for new code on v19; on v18 confirm the team accepts the preview API.
118
+ - **`linkedSignal()`** (introduced v19, experimental) and **`resource()` / `rxResource()` / `httpResource()`** (experimental — `httpResource` from v19.2) for derived & async state. Treat all as experimental on v18-19.
119
+ - **Zoneless** change detection available but **experimental** — `provideExperimentalZonelessChangeDetection()` (becomes stable `provideZonelessChangeDetection()` in v20).
120
+ - **Standalone is the implicit default since v19** (`standalone: true` optional). On v18 and earlier `standalone` defaults to `false` — must be set explicitly.
121
+ - Event replay / incremental hydration (`@defer`-powered) is **developer preview** here (graduates to stable in v20) — relevant only with SSR.