General Angular coding-standards enforcement — naming conventions, standalone-first architecture (v17+), service extraction for business logic, strict TypeScript compiler options, barrel-file risk, and signal-based input()/output() adoption (v17.1+). Version-gated checks read .claude/pilot/stack-profile.json (angular.majorVersion) when present; otherwise ask or infer from angular.json/package.json.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add AgenticPawan/FullStack-Pilot --skill angular-coding-standards --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Angular Coding Standards?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/agenticpawan-angular-coding-standards)More formats (shields.io, HTML) on the badges page.
---
name: angular-coding-standards
description: General Angular coding-standards enforcement — naming conventions, standalone-first architecture (v17+), service extraction for business logic, strict TypeScript compiler options, barrel-file risk, and signal-based input()/output() adoption (v17.1+). Version-gated checks read .claude/pilot/stack-profile.json (angular.majorVersion) when present; otherwise ask or infer from angular.json/package.json.
when_to_use: coding standards, naming convention, file naming, kebab-case, NgModule, standalone component, feature module, service extraction, business logic in component, strict mode, strictTemplates, tsconfig, barrel file, index.ts re-export, circular import, input decorator, output decorator, signal input
applies_to: angular>=15
---
<!-- Version index:
Standalone components/directives/pipes Angular 15+ (opt-in), default schematic Angular 17+
input() / output() signal functions Angular 17.1+ (stable Angular 17.3+)
strictTemplates Angular 15+ (Ivy default)
-->
## Standard IDs
| ID | Severity | What it checks |
|----|----------|-----------------|
| ACS-001 | P2 | File/class naming deviates from Angular style guide (suffix, casing) |
| ACS-002 | P1 | NgModule-based feature module used on a v17+ project instead of standalone |
| ACS-003 | P1 | Business logic embedded in component class instead of an injectable service |
| ACS-004 | P1 | Strict TypeScript compiler options missing or disabled |
| ACS-005 | P2 | Barrel file (`index.ts`) creates circular-import risk or import-cost bloat |
| ACS-006 | P2 | `@Input()`/`@Output()` used on new v17.1+ components instead of `input()`/`output()` |
**Version gating:** before applying ACS-002 or ACS-006, read `.claude/pilot/stack-profile.json` →
`angular.majorVersion` (and minor if recorded). If the file is absent, fall back to
`dependencies["@angular/core"]` in `package.json`. Do not flag ACS-002 on v15/v16 — NgModules are
still the expected default there. Do not flag ACS-006 below v17.1.
---
## Check A — File and class naming (ACS-001)
### Detection
1. Glob `src/**/*.ts` excluding `*.spec.ts`.
2. For each file, confirm the filename is kebab-case and ends in the type suffix matching its
decorator: `*.component.ts` for `@Component`, `*.service.ts` for `@Injectable` business
services, `*.directive.ts` for `@Directive`, `*.pipe.ts` for `@Pipe`, `*.guard.ts` for
`CanActivate`/functional guards, `*.resolver.ts` for resolvers.
3. Confirm the exported class name is PascalCase and ends with the matching suffix
(`UserProfileComponent`, `AuthService`), not abbreviated (`UsrPrflCmp`) or suffix-free
(`UserProfile`).
4. Flag mismatches between filename and class suffix (e.g. `user-profile.ts` exporting
`UserProfileComponent`).
### BAD — inconsistent naming
```typescript
// File: userProfile.ts (camelCase filename, no suffix, no type suffix on class)
@Component({
selector: 'app-user-profile',
templateUrl: './userProfile.html'
})
export class UserProfile {}
```
### GOOD — Angular style guide naming
```typescript
// File: user-profile.component.ts
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrl: './user-profile.component.scss'
})
export class UserProfileComponent {}
```
---
## Check B — Standalone-first architecture on Angular 17+ (ACS-002)
### Detection
1. Read `.claude/pilot/stack-profile.json` → `angular.majorVersion`. If `< 17`, skip this check
entirely (NgModule feature modules are the expected pattern on v15/v16).
2. On v17+, glob `src/**/*.module.ts` excluding `app.module.ts` migration remnants already
flagged elsewhere.
3. For each feature module found, check whether its declared components/directives/pipes set
`standalone: true` (or omit `standalone` on v19+, where it defaults to `true`) — if not, and
the module exists purely to declare/export a feature area, flag it as a new-project deviation.
4. New route entries added via `loadChildren: () => import('./feature/feature.module')` on a
v17+ project are a strong signal of a newly-authored NgModule; prioritize those.
### BAD — new NgModule feature module on Angular 18 project
```typescript
// orders.module.ts — newly added, project is on Angular 18
@NgModule({
declarations: [OrdersListComponent, OrderDetailComponent],
imports: [CommonModule, RouterModule.forChild(ordersRoutes)],
exports: [OrdersListComponent]
})
export class OrdersModule {}
```
### GOOD — standalone components with lazy routes
```typescript
// orders.routes.ts
export const ordersRoutes: Routes = [
{ path: '', loadComponent: () =>
import('./orders-list.component').then(m => m.OrdersListComponent) },
{ path: ':id', loadComponent: () =>
import('./order-detail.component').then(m => m.OrderDetailComponent) }
];
```
```typescript
// orders-list.component.ts
@Component({
selector: 'app-orders-list',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './orders-list.component.html'
})
export class OrdersListComponent {}
```
---
## Check C — Business logic extracted to services (ACS-003)
### Detection
1. Scan component class bodies (`*.component.ts`) for methods containing: `HttpClient` calls,
multi-step data transformation (`.filter().map().reduce()` chains over domain data), direct
`localStorage`/`sessionStorage` access, or validation/calculation logic longer than ~10 lines.
2. Flag components with no corresponding injected `*Service` where such logic appears directly
in a component method rather than delegated.
3. A component may keep *presentation* logic (formatting a value already computed by a service,
deriving a computed signal from `@Input`) — only flag domain/business logic and I/O.
### BAD — pricing logic and HTTP call inside the component
```typescript
@Component({ selector: 'app-cart-summary', standalone: true, templateUrl: './cart-summary.component.html' })
export class CartSummaryComponent {
private http = inject(HttpClient);
items = input.required<CartItem[]>();
submitOrder() {
const subtotal = this.items().reduce((sum, i) => sum + i.price * i.qty, 0);
const discount = subtotal > 100 ? subtotal * 0.1 : 0;
const tax = (subtotal - discount) * 0.08;
const total = subtotal - discount + tax;
this.http.post('/api/orders', { items: this.items(), total }).subscribe();
}
}
```
### GOOD — logic delegated to an injectable service
```typescript
@Injectable({ providedIn: 'root' })
export class OrderService {
private http = inject(HttpClient);
calculateTotal(items: CartItem[]): OrderTotal {
const subtotal = items.reduce((sum, i) => sum + i.price * i.qty, 0);
const discount = subtotal > 100 ? subtotal * 0.1 : 0;
const tax = (subtotal - discount) * 0.08;
return { subtotal, discount, tax, total: subtotal - discount + tax };
}
submitOrder(items: CartItem[], total: OrderTotal) {
return this.http.post('/api/orders', { items, total });
}
}
```
```typescript
@Component({ selector: 'app-cart-summary', standalone: true, templateUrl: './cart-summary.component.html' })
export class CartSummaryComponent {
private orderService = inject(OrderService);
items = input.required<CartItem[]>();
total = computed(() => this.orderService.calculateTotal(this.items()));
submitOrder() {
this.orderService.submitOrder(this.items(), this.total()).subscribe();
}
}
```
---
## Check D — Strict TypeScript compiler options (ACS-004)
### Detection
1. Read `tsconfig.json` (base) → `compilerOptions.strict` must be `true`.
2. Read `angular.json` → each project's `architect.build.options.tsConfig` target file →
confirm `angularCompilerOptions.strictTemplates` is `true` (Ivy defaults it to `true`
starting with `strict: true`, but explicit config drift can disable it).
3. Flag any `tsconfig*.json` that overrides `strict: false` or individually disables
`strictNullChecks`/`noImplicitAny` in a way that weakens the base config.
### BAD — strict mode disabled
```json
// tsconfig.json
{
"compilerOptions": {
"strict": false,
"noImplicitAny": false
},
"angularCompilerOptions": {
"strictTemplates": false
}
}
```
### GOOD — strict mode enforced end to end
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"angularCompilerOptions": {
"strictTemplates": true,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true
}
}
```
---
## Check E — Barrel files and circular-import risk (ACS-005)
### Detection
1. Glob `src/**/index.ts`.
2. For each barrel, list its re-exports (`export * from './x'` / `export { X } from './x'`).
3. Flag a barrel as high-risk when: (a) it re-exports more than ~15 symbols from a single
feature area (import-cost bloat — consumers pull in the whole feature graph for one symbol),
or (b) a file inside the same feature directory imports from the barrel that re-exports
itself (`./index`), which is the classic circular-import trigger during bundling/tree-shaking.
4. Prefer deep imports for cross-feature consumption; barrels are acceptable only at a stable,
narrow public-API boundary (e.g. a shared UI-kit library entry point).
### BAD — feature-wide barrel imported from within the same feature
```typescript
// features/orders/index.ts
export * from './orders-list.component';
export * from './order-detail.component';
export * from './order.service';
export * from './order.model';
export * from './order-status.pipe';
// ...12 more re-exports
```
```typescript
// features/orders/order-detail.component.ts
import { OrderService } from './index'; // circular: index.ts re-exports this same directory
```
### GOOD — deep imports within the feature, narrow barrel only at the public boundary
```typescript
// features/orders/order-detail.component.ts
import { OrderService } from './order.service';
import { OrderStatusPipe } from './order-status.pipe';
```
```typescript
// libs/ui-kit/index.ts — narrow, stable public API, not consumed by its own internals
export { ButtonComponent } from './button/button.component';
export { CardComponent } from './card/card.component';
```
---
## Check F — Signal-based input()/output() on Angular 17.1+ (ACS-006)
### Detection
1. Read `.claude/pilot/stack-profile.json` → `angular.majorVersion` (and minor if available).
Skip this check below v17.1 — `input()`/`output()` did not exist yet.
2. Scan newly added or modified `*.component.ts` files for `@Input()` / `@Output()` decorators.
3. Flag decorator usage in **new** components on v17.1+ projects; do not require migrating
pre-existing decorator-based components wholesale (that is the `angular-upgrade-path` skill's
concern) — this check targets newly authored code.
### BAD — decorator-based I/O in a new component on Angular 18
```typescript
@Component({ selector: 'app-badge', standalone: true, templateUrl: './badge.component.html' })
export class BadgeComponent {
@Input() label = '';
@Input({ required: true }) count!: number;
@Output() dismissed = new EventEmitter<void>();
dismiss() {
this.dismissed.emit();
}
}
```
### GOOD — signal-based input()/output()
```typescript
@Component({ selector: 'app-badge', standalone: true, templateUrl: './badge.component.html' })
export class BadgeComponent {
label = input('');
count = input.required<number>();
dismissed = output<void>();
dismiss() {
this.dismissed.emit();
}
}
```
---
## Coding standards checklist
- [ ] File names are kebab-case with the correct type suffix; class names are PascalCase with matching suffix
- [ ] No new NgModule feature modules on Angular 17+ projects (check `angular.majorVersion` first)
- [ ] Business/domain logic and HTTP calls live in injectable services, not component classes
- [ ] `tsconfig.json` has `"strict": true`; `angularCompilerOptions.strictTemplates` is `true`
- [ ] Barrel files are narrow (public API only) and not imported from within their own feature directory
- [ ] New components on Angular 17.1+ use `input()`/`output()` instead of `@Input()`/`@Output()`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!