agent-directives 0.3.0 → 0.4.1
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/README.md +1 -1
- package/directives/adaptive-routing.md +22 -8
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js.map +1 -1
- package/manifest.json +95 -7
- package/package.json +1 -1
- package/rules/angular/coding-style.md +176 -0
- package/rules/angular/components-and-templates.md +113 -15
- package/rules/angular/patterns.md +300 -0
- package/rules/angular/project-structure.md +115 -11
- package/rules/angular/security.md +159 -0
- package/rules/angular/testing.md +198 -15
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-patterns
|
|
3
|
+
description: Concrete Angular application patterns — smart/dumb component split, service-layer ownership, routing/guards/resolvers, HTTP interceptors, and reactive state with signals or RxJS.
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
required: false
|
|
6
|
+
category: angular
|
|
7
|
+
tools:
|
|
8
|
+
- claude
|
|
9
|
+
- copilot
|
|
10
|
+
- codex
|
|
11
|
+
- cursor
|
|
12
|
+
source_urls:
|
|
13
|
+
- https://angular.dev/guide/components
|
|
14
|
+
- https://angular.dev/guide/routing
|
|
15
|
+
- https://angular.dev/guide/http
|
|
16
|
+
- https://angular.dev/guide/di
|
|
17
|
+
- https://angular.dev/guide/signals
|
|
18
|
+
- https://github.com/angular/angular/tree/main/skills/dev-skills/angular-developer/references
|
|
19
|
+
applies_to:
|
|
20
|
+
- src/app/**/*.component.ts
|
|
21
|
+
- src/app/**/*.component.html
|
|
22
|
+
- src/app/**/*.service.ts
|
|
23
|
+
- src/app/**/*.store.ts
|
|
24
|
+
- src/app/**/*.routes.ts
|
|
25
|
+
- src/app/**/*.guard.ts
|
|
26
|
+
- src/app/**/*.resolver.ts
|
|
27
|
+
- src/app/**/*.interceptor.ts
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Angular Patterns Rules
|
|
31
|
+
|
|
32
|
+
**Load when:** Building or reviewing Angular feature code that crosses components and services — data flow, routing, HTTP, forms, or state.
|
|
33
|
+
|
|
34
|
+
## Version Awareness
|
|
35
|
+
|
|
36
|
+
Functional guards/resolvers/interceptors, `inject()`, signal inputs, `resource()`, `linkedSignal`, and `withComponentInputBinding()` are version-gated. Match the project's Angular version before introducing a pattern, and keep an existing file consistent with the style already in it.
|
|
37
|
+
|
|
38
|
+
## Sources
|
|
39
|
+
|
|
40
|
+
- Angular Components Guide — https://angular.dev/guide/components
|
|
41
|
+
- Angular Routing Guide — https://angular.dev/guide/routing
|
|
42
|
+
- Angular HTTP Guide — https://angular.dev/guide/http
|
|
43
|
+
- Angular Dependency Injection Guide — https://angular.dev/guide/di
|
|
44
|
+
- Angular Signals Guide — https://angular.dev/guide/signals
|
|
45
|
+
|
|
46
|
+
## Rules
|
|
47
|
+
|
|
48
|
+
### Smart / dumb component split
|
|
49
|
+
|
|
50
|
+
Smart (container) components own data fetching and state. Dumb (presentational) components receive inputs and emit outputs only — no service injection, no router access.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Smart — owns data
|
|
54
|
+
@Component({
|
|
55
|
+
selector: 'app-user-page',
|
|
56
|
+
standalone: true,
|
|
57
|
+
imports: [UserCardComponent],
|
|
58
|
+
template: `
|
|
59
|
+
@if (userResource.isLoading()) { <app-spinner /> }
|
|
60
|
+
@else if (userResource.error()) { <app-error /> }
|
|
61
|
+
@else { <app-user-card [user]="userResource.value()!" (select)="onSelect($event)" /> }
|
|
62
|
+
`,
|
|
63
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
64
|
+
})
|
|
65
|
+
export class UserPageComponent {
|
|
66
|
+
private userService = inject(UserService);
|
|
67
|
+
userId = input.required<string>();
|
|
68
|
+
|
|
69
|
+
userResource = resource({
|
|
70
|
+
request: () => ({ id: this.userId() }),
|
|
71
|
+
loader: ({ request }) => firstValueFrom(this.userService.getUser(request.id)),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
onSelect(id: string) { /* navigate or update state */ }
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Dumb — pure presentation, no inject(), no router
|
|
80
|
+
@Component({
|
|
81
|
+
selector: 'app-user-card',
|
|
82
|
+
standalone: true,
|
|
83
|
+
template: `<button (click)="select.emit(user().id)">{{ user().name }}</button>`,
|
|
84
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
85
|
+
})
|
|
86
|
+
export class UserCardComponent {
|
|
87
|
+
user = input.required<User>();
|
|
88
|
+
select = output<string>();
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Service layer
|
|
93
|
+
|
|
94
|
+
Services own all data access and business logic. Components delegate — no `HttpClient` in components, no `fetch()` in components.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
@Injectable({ providedIn: 'root' })
|
|
98
|
+
export class UserService {
|
|
99
|
+
private http = inject(HttpClient);
|
|
100
|
+
|
|
101
|
+
getUsers(): Observable<User[]> {
|
|
102
|
+
return this.http.get<User[]>('/api/users');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getUser(id: string): Observable<User> {
|
|
106
|
+
return this.http.get<User>(`/api/users/${id}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- One service per cohesive domain concept. Do not bury unrelated APIs in a god service.
|
|
112
|
+
- Scope a service to a component/route subtree (component `providers: [...]`) only when its lifecycle must follow that subtree. Otherwise prefer `providedIn: 'root'`.
|
|
113
|
+
|
|
114
|
+
### Reactive state
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Local state
|
|
118
|
+
count = signal(0);
|
|
119
|
+
doubled = computed(() => this.count() * 2);
|
|
120
|
+
selectedItem = linkedSignal(() => this.items()[0]);
|
|
121
|
+
|
|
122
|
+
// Bridge an Observable into a signal
|
|
123
|
+
users = toSignal(this.userService.getUsers(), { initialValue: [] });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- Derived values live in `computed` / `linkedSignal` — never in a separate writable signal updated by `effect`.
|
|
127
|
+
- Use `takeUntilDestroyed(this.destroyRef)` for manual subscriptions:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
export class UserComponent {
|
|
131
|
+
private destroyRef = inject(DestroyRef);
|
|
132
|
+
private userService = inject(UserService);
|
|
133
|
+
|
|
134
|
+
ngOnInit() {
|
|
135
|
+
this.userService.updates$
|
|
136
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
137
|
+
.subscribe(update => this.handleUpdate(update));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Routing
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// app.routes.ts (or feature.routes.ts)
|
|
146
|
+
export const routes: Routes = [
|
|
147
|
+
{ path: '', component: HomeComponent },
|
|
148
|
+
{
|
|
149
|
+
path: 'admin',
|
|
150
|
+
canMatch: [authGuard], // canMatch prevents the chunk from loading at all
|
|
151
|
+
loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
path: 'users/:id',
|
|
155
|
+
resolve: { user: userResolver },
|
|
156
|
+
loadComponent: () => import('./users/user-detail.component').then(m => m.UserDetailComponent),
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- Use `canMatch` (not `canActivate`) for sensitive routes — the route module never loads for unauthorized users, so the code isn't shipped to them.
|
|
162
|
+
- Lazy-load every feature with `loadChildren` (or `loadComponent` for a single route).
|
|
163
|
+
- Pre-fetch with `resolve` when the destination cannot meaningfully render without the data.
|
|
164
|
+
- Enable component input binding once at app level (`withComponentInputBinding()`), then bind route params via `input()`:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
export class UserDetailComponent {
|
|
168
|
+
id = input.required<string>(); // route param :id binds automatically
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Functional guards and resolvers
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
export const authGuard: CanActivateFn = () => {
|
|
176
|
+
const auth = inject(AuthService);
|
|
177
|
+
return auth.isAuthenticated()
|
|
178
|
+
? true
|
|
179
|
+
: inject(Router).createUrlTree(['/login']);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const userResolver: ResolveFn<User> = (route) =>
|
|
183
|
+
inject(UserService).getUser(route.paramMap.get('id')!);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
- Prefer functional `CanActivateFn` / `CanMatchFn` / `ResolveFn` / `HttpInterceptorFn` over class-based equivalents on standalone-first projects.
|
|
187
|
+
|
|
188
|
+
### HTTP interceptors
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
|
192
|
+
const token = inject(AuthService).token();
|
|
193
|
+
if (!token) return next(req);
|
|
194
|
+
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// app.config.ts
|
|
198
|
+
provideHttpClient(withInterceptors([authInterceptor, errorInterceptor, retryInterceptor]))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- Register cross-cutting concerns (auth, error mapping, retries, logging) as interceptors. Do not duplicate header logic across services.
|
|
202
|
+
- Keep interceptors small and composable; one concern per interceptor.
|
|
203
|
+
|
|
204
|
+
### Forms
|
|
205
|
+
|
|
206
|
+
Match the project's existing form strategy. For complex validation, prefer Reactive Forms. For v21+ projects starting fresh, Signal Forms are appropriate.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Reactive Forms — standard approach for most apps
|
|
210
|
+
export class LoginComponent {
|
|
211
|
+
private fb = inject(FormBuilder);
|
|
212
|
+
|
|
213
|
+
form = this.fb.nonNullable.group({
|
|
214
|
+
email: ['', [Validators.required, Validators.email]],
|
|
215
|
+
password: ['', [Validators.required, Validators.minLength(8)]],
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
submit() {
|
|
219
|
+
if (this.form.invalid) return;
|
|
220
|
+
const { email, password } = this.form.getRawValue();
|
|
221
|
+
// ...
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- Use `fb.nonNullable` / `NonNullableFormBuilder` so controls are typed as `T` rather than `T | null`.
|
|
227
|
+
- Avoid mixing reactive and template-driven forms in the same form.
|
|
228
|
+
|
|
229
|
+
### Async data with `resource()`
|
|
230
|
+
|
|
231
|
+
Use `resource()` for reactive async fetching that depends on signals — it manages loading/error/value automatically and reloads when the request signal changes.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
export class UserDetailComponent {
|
|
235
|
+
userId = input.required<string>();
|
|
236
|
+
|
|
237
|
+
userResource = resource({
|
|
238
|
+
request: () => ({ id: this.userId() }),
|
|
239
|
+
loader: ({ request }) =>
|
|
240
|
+
firstValueFrom(inject(UserService).getUser(request.id)),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
State: `userResource.value()`, `userResource.isLoading()`, `userResource.error()`, `userResource.reload()`.
|
|
246
|
+
|
|
247
|
+
### RxJS operator choice
|
|
248
|
+
|
|
249
|
+
| Operator | Use for |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `switchMap` | Search, navigation, latest-wins |
|
|
252
|
+
| `mergeMap` | Independent parallel requests |
|
|
253
|
+
| `exhaustMap` | Form submits — ignore re-clicks until current completes |
|
|
254
|
+
| `concatMap` | Ordered queueing |
|
|
255
|
+
|
|
256
|
+
Always end an HTTP-backed inner stream with `catchError` so the outer stream survives failures.
|
|
257
|
+
|
|
258
|
+
### Rendering strategies
|
|
259
|
+
|
|
260
|
+
- **CSR** (default) — standard SPA.
|
|
261
|
+
- **SSR + Hydration** — `ng add @angular/ssr`. Improves first paint and SEO.
|
|
262
|
+
- **SSG / Prerender** — for content-heavy public routes.
|
|
263
|
+
|
|
264
|
+
Under SSR, never touch `window`, `document`, `localStorage`, or `navigator` directly — gate with `isPlatformBrowser(this.platformId)` or inject via the `DOCUMENT` token.
|
|
265
|
+
|
|
266
|
+
### Accessibility
|
|
267
|
+
|
|
268
|
+
- Reuse Angular CDK primitives (Listbox, Menu, Dialog, Overlay, Tree, A11y `LiveAnnouncer`/`FocusTrap`) rather than re-implementing ARIA wiring.
|
|
269
|
+
- Style ARIA state attributes rather than toggling presentation classes:
|
|
270
|
+
|
|
271
|
+
```css
|
|
272
|
+
[aria-selected="true"] { background: var(--color-selected); }
|
|
273
|
+
[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Deep-Dive Reference Materials
|
|
277
|
+
|
|
278
|
+
Coding agents should fetch the raw text of these references programmatically when writing or modifying general architectural patterns, forms, routing, or HTTP mechanics:
|
|
279
|
+
|
|
280
|
+
- **Form Management:** Signal-based forms (v21+), template-driven, and reactive forms. Read [signal-forms.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/signal-forms.md), [template-driven-forms.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/template-driven-forms.md), and [reactive-forms.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/reactive-forms.md)
|
|
281
|
+
- **Route Definitions:** URL paths, static vs dynamic segments, wildcards, and redirects. Read [define-routes.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/define-routes.md)
|
|
282
|
+
- **Route Loading & Outlets:** Lazy loading strategies and named `<router-outlet>` routing. Read [loading-strategies.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/loading-strategies.md) and [show-routes-with-outlets.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/show-routes-with-outlets.md)
|
|
283
|
+
- **Navigation & Access Control:** Declarative or programmatic routing, and route security with guards. Read [navigate-to-routes.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/navigate-to-routes.md) and [route-guards.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/route-guards.md)
|
|
284
|
+
- **Pre-fetching & Lifecycle:** Route resolvers (`ResolveFn`) and navigation lifecycle stages. Read [data-resolvers.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/data-resolvers.md) and [router-lifecycle.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/router-lifecycle.md)
|
|
285
|
+
- **Rendering Strategies:** SSR with hydration, SSG (prerendering), and CSR. Read [rendering-strategies.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/rendering-strategies.md)
|
|
286
|
+
- **Transition Animations:** Customizing View Transitions API routes. Read [route-animations.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/route-animations.md)
|
|
287
|
+
|
|
288
|
+
## Anti-patterns to refuse
|
|
289
|
+
|
|
290
|
+
- `HttpClient` injected into a presentational component.
|
|
291
|
+
- Subscriptions in components without `takeUntilDestroyed` (or a documented manual `ngOnDestroy` cleanup).
|
|
292
|
+
- `canActivate` on a route whose entire chunk should be withheld from unauthorized users — use `canMatch`.
|
|
293
|
+
- Class-based `HTTP_INTERCEPTORS` providers on a standalone-first project.
|
|
294
|
+
- Manual `subscribe()` + assign-to-property to flow Observable data into the view when `async` pipe or `toSignal` covers the use case.
|
|
295
|
+
|
|
296
|
+
## Evidence
|
|
297
|
+
|
|
298
|
+
- Targeted tests for the changed behavior (see `rules/angular/testing.md`).
|
|
299
|
+
- `ng lint` (or project equivalent) on touched files.
|
|
300
|
+
- `ng build` to verify typed templates and route configuration compile.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: angular-project-structure
|
|
3
|
-
description: Angular workspace and
|
|
4
|
-
version: 1.
|
|
3
|
+
description: Concrete Angular workspace, file-naming, feature-folder, and provider-bootstrapping standards for agents working in Angular applications.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
required: false
|
|
6
6
|
category: angular
|
|
7
7
|
tools:
|
|
@@ -13,33 +13,137 @@ source_urls:
|
|
|
13
13
|
- https://angular.dev/style-guide
|
|
14
14
|
- https://angular.dev/tools/cli
|
|
15
15
|
- https://angular.dev/reference/configs/workspace-config
|
|
16
|
+
- https://angular.dev/guide/ngmodules/standalone
|
|
17
|
+
- https://github.com/angular/angular/tree/main/skills/dev-skills/angular-developer/references
|
|
16
18
|
applies_to:
|
|
17
19
|
- angular.json
|
|
18
20
|
- package.json
|
|
21
|
+
- src/main.ts
|
|
19
22
|
- src/app/**/*.ts
|
|
20
23
|
- src/app/**/*.html
|
|
21
24
|
---
|
|
22
25
|
|
|
23
26
|
# Angular Project Structure Rules
|
|
24
27
|
|
|
25
|
-
**Load when:** The project contains `angular.json` or `@angular/core`, or the task touches Angular app structure, routes,
|
|
28
|
+
**Load when:** The project contains `angular.json` or `@angular/core`, or the task touches Angular app structure, routes, file layout, providers, or bootstrap configuration.
|
|
26
29
|
|
|
27
|
-
##
|
|
30
|
+
## Version Awareness
|
|
31
|
+
|
|
32
|
+
Confirm the Angular version (`ng version` or `package.json`) before suggesting structure. Standalone APIs and the v17+ application builder are now defaults; do not invent NgModules on a standalone-first project, and do not migrate a working NgModule layout as a side effect of an unrelated change.
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
## Sources
|
|
30
35
|
|
|
31
36
|
- Angular Style Guide — https://angular.dev/style-guide
|
|
32
37
|
- Angular CLI Overview — https://angular.dev/tools/cli
|
|
33
38
|
- Angular Workspace Configuration — https://angular.dev/reference/configs/workspace-config
|
|
39
|
+
- Standalone APIs — https://angular.dev/guide/ngmodules/standalone
|
|
34
40
|
|
|
35
41
|
## Rules
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
+
### File and folder naming
|
|
44
|
+
|
|
45
|
+
Follow Angular CLI conventions — one artifact per file. Use kebab-case file names and PascalCase class names:
|
|
46
|
+
|
|
47
|
+
- `user-profile.component.ts` + `user-profile.component.html` + `user-profile.component.scss` + `user-profile.component.spec.ts`
|
|
48
|
+
- `user.service.ts`, `auth.guard.ts`, `date-format.pipe.ts`, `logging.interceptor.ts`, `user.resolver.ts`
|
|
49
|
+
- Test files live next to the unit they test, named `<name>.spec.ts`.
|
|
50
|
+
- Generate scaffolding with the CLI rather than handwriting it: `ng generate component features/users/user-card --change-detection=OnPush --inline-style=false`.
|
|
51
|
+
|
|
52
|
+
### Feature folders
|
|
53
|
+
|
|
54
|
+
Group by feature, then by artifact. Do not create catch-all `shared/` or `utils/` buckets for unrelated code.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
src/app/
|
|
58
|
+
core/ # app-wide singletons: auth, http interceptors, app-level guards
|
|
59
|
+
shared/ # reusable presentational components, pipes, directives
|
|
60
|
+
features/
|
|
61
|
+
users/
|
|
62
|
+
users.routes.ts
|
|
63
|
+
user-list/
|
|
64
|
+
user-list.component.{ts,html,scss,spec.ts}
|
|
65
|
+
user-detail/
|
|
66
|
+
user-detail.component.{ts,html,scss,spec.ts}
|
|
67
|
+
user.service.ts
|
|
68
|
+
user.model.ts
|
|
69
|
+
auth/
|
|
70
|
+
auth.routes.ts
|
|
71
|
+
...
|
|
72
|
+
app.config.ts # application providers (router, http, etc.)
|
|
73
|
+
app.routes.ts # top-level routes
|
|
74
|
+
app.component.ts # root component
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Keep route configuration co-located with the feature it serves (`users.routes.ts`), and lazy-load via `loadChildren`.
|
|
78
|
+
- Place app-wide providers (`provideRouter`, `provideHttpClient`, interceptors, error handlers) in `app.config.ts`. Bootstrap from `main.ts` with `bootstrapApplication(AppComponent, appConfig)`.
|
|
79
|
+
- A new directory or top-level folder must be justified by existing project evidence. Follow project routing, state-management, and shared-library boundaries before adding new locations.
|
|
80
|
+
|
|
81
|
+
### Standalone bootstrap
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// main.ts
|
|
85
|
+
bootstrapApplication(AppComponent, appConfig).catch(console.error);
|
|
86
|
+
|
|
87
|
+
// app.config.ts
|
|
88
|
+
export const appConfig: ApplicationConfig = {
|
|
89
|
+
providers: [
|
|
90
|
+
provideRouter(routes, withComponentInputBinding(), withViewTransitions()),
|
|
91
|
+
provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
|
|
92
|
+
provideAnimationsAsync(),
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- Use functional providers (`provideRouter`, `provideHttpClient`, `provideAnimationsAsync`) over the legacy module forms on standalone-first projects.
|
|
98
|
+
- Register HTTP interceptors functionally via `withInterceptors([...])`. Class-based `HTTP_INTERCEPTORS` multi-providers are reserved for NgModule-centered projects.
|
|
99
|
+
|
|
100
|
+
### Routing layout
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// app.routes.ts
|
|
104
|
+
export const routes: Routes = [
|
|
105
|
+
{ path: '', pathMatch: 'full', redirectTo: 'home' },
|
|
106
|
+
{ path: 'home', loadComponent: () => import('./features/home/home.component').then(m => m.HomeComponent) },
|
|
107
|
+
{
|
|
108
|
+
path: 'users',
|
|
109
|
+
loadChildren: () => import('./features/users/users.routes').then(m => m.USERS_ROUTES),
|
|
110
|
+
},
|
|
111
|
+
{ path: '**', loadComponent: () => import('./shared/not-found/not-found.component').then(m => m.NotFoundComponent) },
|
|
112
|
+
];
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Lazy-load every feature area with `loadChildren` (or `loadComponent` for a single route). Eagerly loading feature trees inflates the initial bundle.
|
|
116
|
+
- Use `withComponentInputBinding()` so route params and query params bind to component `input()` signals automatically.
|
|
117
|
+
- Put guards/resolvers next to the route file (`users.guards.ts`, `user.resolver.ts`), not in a global `guards/` bucket.
|
|
118
|
+
|
|
119
|
+
### Workspace and config files
|
|
120
|
+
|
|
121
|
+
- Treat `angular.json`, `tsconfig*.json`, builder targets, and `package.json` script changes as project-wide changes. Keep edits narrow and verify with the configured command (`ng build`, `ng test`, `ng lint`).
|
|
122
|
+
- Do not switch the application builder, change `target`/`module` settings, or rename top-level paths casually — those are coordinated migrations.
|
|
123
|
+
- `environments/environment*.ts` files describe configuration shape, not real secrets. Inject production secrets via CI/CD, not source.
|
|
124
|
+
|
|
125
|
+
### Cross-ecosystem hygiene
|
|
126
|
+
|
|
127
|
+
- Do not add React, Vue, or other-framework conventions to an Angular project. If shared code must straddle frameworks, isolate it behind a framework-neutral library.
|
|
128
|
+
- Match the project's existing module system (ESM vs CommonJS) and import style. Use path aliases from `tsconfig.json` where the project already defines them.
|
|
129
|
+
|
|
130
|
+
## Deep-Dive Reference Materials
|
|
131
|
+
|
|
132
|
+
Coding agents should fetch the raw text of these references programmatically when planning workspace boundaries, scaffolding, dependency injection structures, or upgrades:
|
|
133
|
+
|
|
134
|
+
- **CLI Scaffolding:** CLI command syntax and scaffolding options. Read [cli.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/cli.md)
|
|
135
|
+
- **Creating Services:** Generating services, the `providedIn: 'root'` option, and injection guidelines. Read [creating-services.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/creating-services.md)
|
|
136
|
+
- **Defining Providers:** Token definition patterns (`InjectionToken`, `useValue`, `useFactory`). Read [defining-providers.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/defining-providers.md)
|
|
137
|
+
- **Hierarchical Injectors:** Understanding injector layers (`EnvironmentInjector`, `ElementInjector`), resolution rules, and `viewProviders`. Read [hierarchical-injectors.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/hierarchical-injectors.md)
|
|
138
|
+
- **Migrations & Upgrades:** Modernizing older code bases via automated migrations. Read [migrations.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/migrations.md)
|
|
139
|
+
- **Angular MCP Server:** Supported features and configuration of the Model Context Protocol language tools. Read [mcp.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/mcp.md)
|
|
140
|
+
|
|
141
|
+
## Anti-patterns to refuse
|
|
142
|
+
|
|
143
|
+
- A single `shared/` directory accumulating unrelated services, pipes, components, and constants.
|
|
144
|
+
- New NgModules created on a standalone-first project just to host providers — use `app.config.ts` or route-level `providers`.
|
|
145
|
+
- Eagerly importing feature components into `app.routes.ts` instead of `loadChildren` / `loadComponent`.
|
|
146
|
+
- Editing `angular.json` builders, `tsconfig` paths, or `package.json` scripts as part of an unrelated component or service change.
|
|
43
147
|
|
|
44
148
|
## Evidence
|
|
45
149
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-security
|
|
3
|
+
description: Concrete Angular security rules — XSS prevention, HttpClient discipline, secret handling, route guards, and SSR safety.
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
required: false
|
|
6
|
+
category: angular
|
|
7
|
+
tools:
|
|
8
|
+
- claude
|
|
9
|
+
- copilot
|
|
10
|
+
- codex
|
|
11
|
+
- cursor
|
|
12
|
+
source_urls:
|
|
13
|
+
- https://angular.dev/best-practices/security
|
|
14
|
+
- https://angular.dev/api/platform-browser/DomSanitizer
|
|
15
|
+
- https://angular.dev/guide/http/security
|
|
16
|
+
- https://angular.dev/guide/ssr
|
|
17
|
+
- https://github.com/angular/angular/tree/main/skills/dev-skills/angular-developer/references
|
|
18
|
+
applies_to:
|
|
19
|
+
- src/app/**/*.component.ts
|
|
20
|
+
- src/app/**/*.component.html
|
|
21
|
+
- src/app/**/*.service.ts
|
|
22
|
+
- src/app/**/*.interceptor.ts
|
|
23
|
+
- src/app/**/*.guard.ts
|
|
24
|
+
- src/app/**/*.routes.ts
|
|
25
|
+
- src/environments/**/*.ts
|
|
26
|
+
- src/**/*.server.ts
|
|
27
|
+
- server.ts
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Angular Security Rules
|
|
31
|
+
|
|
32
|
+
**Load when:** Touching code that handles user input, renders untrusted HTML, calls external APIs, manages auth/tokens, configures route guards, or runs under SSR.
|
|
33
|
+
|
|
34
|
+
## Version Awareness
|
|
35
|
+
|
|
36
|
+
Sanitization, route guard, and SSR APIs evolve between versions — verify the version with `ng version` before suggesting an API. Do not introduce APIs that don't exist in the project's Angular version, and do not rip out a guard or sanitization step as a side effect of an unrelated change.
|
|
37
|
+
|
|
38
|
+
## Sources
|
|
39
|
+
|
|
40
|
+
- Angular Security Best Practices — https://angular.dev/best-practices/security
|
|
41
|
+
- DomSanitizer API — https://angular.dev/api/platform-browser/DomSanitizer
|
|
42
|
+
- HttpClient Security — https://angular.dev/guide/http/security
|
|
43
|
+
- Angular SSR Guide — https://angular.dev/guide/ssr
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
### XSS prevention
|
|
48
|
+
|
|
49
|
+
Angular auto-sanitizes interpolation and property bindings. Do not bypass the sanitizer with user-controlled input.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// WRONG — bypasses sanitization, classic XSS
|
|
53
|
+
this.safeHtml = this.sanitizer.bypassSecurityTrustHtml(userInput);
|
|
54
|
+
|
|
55
|
+
// CORRECT — explicit sanitization, returns string with dangerous content stripped
|
|
56
|
+
this.safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, userInput);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Never call `bypassSecurityTrustHtml` / `bypassSecurityTrustScript` / `bypassSecurityTrustStyle` / `bypassSecurityTrustUrl` / `bypassSecurityTrustResourceUrl` on user-controlled input. If you must bypass, the source must be a static, trusted constant, with a comment explaining why.
|
|
60
|
+
- Avoid `[innerHTML]` for untrusted content. Use `{{ value }}` interpolation or `[innerText]` for plain text. If rich content is required, render it through a sanitizing pipe.
|
|
61
|
+
- Never bind `[href]` or `[src]` directly to user-provided URLs without scheme validation — Angular sanitizes some URL contexts but not all.
|
|
62
|
+
- Never build templates by string concatenation with user data.
|
|
63
|
+
|
|
64
|
+
### HttpClient discipline
|
|
65
|
+
|
|
66
|
+
Use `HttpClient` exclusively — not raw `fetch()` or `XHR` — so interceptors (auth headers, error mapping, retry, logging) apply uniformly.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// WRONG — bypasses interceptors entirely
|
|
70
|
+
const res = await fetch('/api/users');
|
|
71
|
+
|
|
72
|
+
// CORRECT
|
|
73
|
+
users$ = this.http.get<User[]>('/api/users');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Attach auth tokens via an interceptor, never by hand on every call. One source of truth for `Authorization` headers.
|
|
77
|
+
- Type and validate API responses. Treat external data as `unknown` at the boundary and narrow with a schema (Zod / io-ts / hand-rolled guard) before letting it flow into typed signals or components.
|
|
78
|
+
- Never log full HTTP responses that may contain tokens, PII, or credentials. Redact before logging.
|
|
79
|
+
- For SSR fetches, prefer relative URLs (or the configured base URL) so the server doesn't re-hit the public host.
|
|
80
|
+
|
|
81
|
+
### Secret and config management
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// WRONG — hardcoded secret in source
|
|
85
|
+
const apiKey = 'sk-live-xxxxxxxxxxxxxxxxxxxxxxxx';
|
|
86
|
+
|
|
87
|
+
// CORRECT — env config shape, real value injected by CI/CD
|
|
88
|
+
import { environment } from '../environments/environment';
|
|
89
|
+
const apiKey = environment.apiKey;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Treat `environments/environment*.ts` as configuration shape, not as a place to commit production secrets. Real secrets come from CI/CD env vars or a secret manager.
|
|
93
|
+
- Do not introduce `process.env.*` reads into browser-bound code unless the build pipeline is configured to inline them safely.
|
|
94
|
+
- Never commit `.env` files containing real credentials.
|
|
95
|
+
|
|
96
|
+
### Route guards
|
|
97
|
+
|
|
98
|
+
Every authenticated or role-restricted route must have a guard. Hiding a UI element is not access control.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
{
|
|
102
|
+
path: 'admin',
|
|
103
|
+
canMatch: [authGuard, roleGuard('admin')],
|
|
104
|
+
loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- Use `canMatch` for sensitive routes so the route's lazy chunk is not loaded at all for unauthorized users.
|
|
109
|
+
- Use `canDeactivate` to prevent accidental data loss on unsaved forms — but never as a security mechanism.
|
|
110
|
+
- Guards must trust a server-side check too. The server is the security boundary; the client guard is a UX gate.
|
|
111
|
+
|
|
112
|
+
### Auth tokens
|
|
113
|
+
|
|
114
|
+
- Store tokens in HTTP-only cookies when the backend supports it. If using `localStorage` / `sessionStorage`, accept the XSS risk and mitigate accordingly.
|
|
115
|
+
- Refresh tokens belong in HTTP-only cookies or a dedicated secure storage layer — never in plain `localStorage`.
|
|
116
|
+
- Clear all auth state on logout, including in-memory signals, interceptors caches, and storage.
|
|
117
|
+
|
|
118
|
+
### SSR safety
|
|
119
|
+
|
|
120
|
+
When the app runs under Angular SSR:
|
|
121
|
+
|
|
122
|
+
- Never access `window`, `document`, `localStorage`, `sessionStorage`, or `navigator` directly. Gate browser-only code with `isPlatformBrowser(this.platformId)` or inject the `DOCUMENT` token for DOM access.
|
|
123
|
+
- Sanitize user input on the server too — DOM-based XSS is reachable through SSR'd markup.
|
|
124
|
+
- Do not place server-only secrets into `TransferState` — anything serialized there ships to the client.
|
|
125
|
+
- Watch for hydration mismatches: server-rendered markup must equal initial client render. Diverging output is both a bug and a possible injection vector.
|
|
126
|
+
|
|
127
|
+
### Content Security Policy
|
|
128
|
+
|
|
129
|
+
Configure CSP headers server-side. Specifically:
|
|
130
|
+
|
|
131
|
+
- Avoid `unsafe-inline` in `script-src`. If SSR emits inline state, use Angular's CSP nonce support and propagate the nonce on every inline tag.
|
|
132
|
+
- Avoid `unsafe-eval` — Angular AOT does not need it.
|
|
133
|
+
- Restrict `connect-src` to the API origins the app actually uses.
|
|
134
|
+
|
|
135
|
+
### Dependencies
|
|
136
|
+
|
|
137
|
+
- Treat `npm audit` / `pnpm audit` / `yarn audit` findings as real signals. Resolve high-severity issues before shipping touched code paths.
|
|
138
|
+
- Do not pin to versions with known active CVEs as a workaround for a feature change.
|
|
139
|
+
|
|
140
|
+
## Deep-Dive Reference Materials
|
|
141
|
+
|
|
142
|
+
Coding agents should fetch the raw text of these references programmatically when verifying accessibility standards and headless component configurations:
|
|
143
|
+
|
|
144
|
+
- **Angular Aria Components:** Building headless, accessible components (Accordion, Listbox, Combobox, Menu, Tabs, Toolbar, Tree, Grid) and styling ARIA attributes. Read [angular-aria.md](https://raw.githubusercontent.com/angular/angular/main/skills/dev-skills/angular-developer/references/angular-aria.md)
|
|
145
|
+
|
|
146
|
+
## Anti-patterns to refuse
|
|
147
|
+
|
|
148
|
+
- `bypassSecurityTrust*` on a value derived in any way from user input.
|
|
149
|
+
- `[innerHTML]` bound to a request body, query param, or form value without sanitization.
|
|
150
|
+
- `fetch('/api/...')` in a service that already has `HttpClient` available.
|
|
151
|
+
- Auth checks done only by hiding a button — no guard, no server-side check.
|
|
152
|
+
- `window.localStorage.getItem(...)` in code that also runs under SSR.
|
|
153
|
+
|
|
154
|
+
## Evidence
|
|
155
|
+
|
|
156
|
+
- For changes that affect rendering of untrusted content: a test asserting the rendered output does not contain the dangerous payload.
|
|
157
|
+
- For auth/guard changes: a `RouterTestingHarness` test that exercises both allowed and denied branches.
|
|
158
|
+
- For interceptor changes: a `HttpTestingController` test confirming the header / retry / error mapping behavior.
|
|
159
|
+
- For dependency upgrades touching security: the audit report after the change.
|