Angular (17+)
References
- •Angular Documentation
- •Angular Signals
- •Angular CLI
- •Angular Material
- •RxJS Documentation
- •TypeScript Handbook
- •Angular ESLint
- •Jest (Angular)
- •Cypress
- •gradle-node-plugin
Code Quality Checks
Check project for existing scripts first. Use npm run <script> if available. Always use ./gradlew, never bare gradle.
# Frontend (check project scripts first; fall back to these) npm run lint -- --fix # or: npx ng lint --fix npm run build # or: npx ng build npm test # Jest (or project runner) npm run e2e # Cypress (if present) # Backend (Spring Boot — always run for embedded projects) ./gradlew spotlessApply # 1. Format ./gradlew compileJava # 2. Compile ./gradlew checkstyleMain spotbugsMain pmdMain # 3. Static analysis ./gradlew test # 4. Tests ./gradlew dependencyCheckAnalyze # 5. Vulnerability scan ./gradlew build # 6. Full build (includes Angular via gradle-node-plugin)
Project Structure
All new Angular apps MUST be embedded in Spring Boot. Angular source lives in src/main/webapp/, build output goes to build/resources/main/static (Gradle).
project-root/ ├── src/ │ ├── main/ │ │ ├── java/com/example/app/ │ │ │ ├── Application.java │ │ │ ├── domain/ │ │ │ │ ├── model/ # Entities, value objects, enums │ │ │ │ ├── port/in/ # Inbound ports (use cases) │ │ │ │ ├── port/out/ # Outbound ports (driven) │ │ │ │ └── service/ # Domain service implementations │ │ │ ├── adapter/ │ │ │ │ ├── in/web/ # Controllers, request/response DTOs │ │ │ │ └── out/persistence/# Repositories │ │ │ └── config/ # @Configuration classes │ │ ├── resources/ │ │ │ └── application.yml │ │ └── webapp/ # Angular source │ │ ├── app/ │ │ │ ├── core/ # Singleton services, guards, interceptors │ │ │ ├── shared/ # Reusable components, directives, pipes │ │ │ ├── features/ │ │ │ │ └── <feature>/ │ │ │ │ ├── components/ │ │ │ │ ├── services/ │ │ │ │ ├── models/ │ │ │ │ └── <feature>.routes.ts │ │ │ ├── app.component.ts │ │ │ ├── app.routes.ts │ │ │ └── app.config.ts │ │ ├── index.html │ │ ├── main.ts │ │ └── styles.scss │ └── test/java/com/example/app/ ├── angular.json ├── package.json ├── tsconfig.json ├── tsconfig.app.json ├── build.gradle ├── gradlew / gradlew.bat └── gradle/wrapper/
Testing
Check project for existing test framework first.
- •Unit tests: Jest for new projects. Use
@angular-builders/jestorjest-preset-angular. - •E2E: Cypress for new projects.
- •Component tests: Use Angular
TestBed. Prefer shallow tests with stub children. - •Service tests: Use
TestBed.inject(). Mock HTTP withHttpClientTestingModule/provideHttpClientTesting(). - •Every new component, service, pipe, and directive must have a corresponding test.
- •Test naming:
should do X when condition Y. - •Use
ComponentFixtureandDebugElementfor DOM assertions.
See reference/examples.md for Jest setup, TestBed patterns, and Cypress configuration.
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Components | PascalCase + Component suffix | UserListComponent |
| Services | PascalCase + Service suffix | AuthService |
| Directives | PascalCase + Directive suffix | HighlightDirective |
| Pipes | PascalCase + Pipe suffix | DateFormatPipe |
| Guards | PascalCase + Guard suffix or camelCase fn | AuthGuard, authGuard |
| Interceptors | PascalCase + Interceptor suffix or fn | TokenInterceptor |
| Models/Interfaces | PascalCase (no I prefix) | User, OrderItem |
| Files | kebab-case + type suffix | user-list.component.ts |
| Selectors | app- prefix, kebab-case | app-user-list |
| Observables | camelCase with $ suffix | users$, loading$ |
| Signals | camelCase, NO $ suffix | count, isLoading |
| Feature folders | kebab-case | user-management/ |
| Route files | <feature>.routes.ts | user.routes.ts |
| Constants | SCREAMING_SNAKE | MAX_RETRIES |
Idioms
- •Standalone components by default. No NgModules for new code. Use
importsarray on@Component. - •DI style — mixed.
inject()for functional contexts (guards, interceptors, resolvers). Check project first for components/services — use whichever pattern is established. New projects:inject(). - •Signals for synchronous state. Use
signal(),computed(),effect(). No$suffix on signals. - •Observables for async streams. HTTP calls, WebSockets, router events. Use
$suffix. - •
OnPushchange detection on all new components. - •SCSS for new projects. Check project for existing preprocessor.
- •
strictTemplates: truemandatory intsconfig.jsonfor new projects. - •SPA only for new projects. Do not change existing SSR setups.
- •Typed reactive forms. Use
FormControl<T>,FormGroup,FormArraywith explicit types. - •
AsyncPipeortoSignal()in templates — never manual.subscribe()for template data. - •
DestroyRef+takeUntilDestroyed()for subscription cleanup.
See reference/examples.md for standalone component, signal service, and DI patterns.
Design Patterns
- •Facade services — Encapsulate feature state with signals. Expose
signal()andcomputed()values. Mutate via methods. - •Smart/dumb components — Container components handle logic and inject services. Presentational components use
@Input/@Output(or signal inputs) only. - •Feature-based modules — Group by domain feature, not by technical layer.
- •Lazy-loaded routes —
loadComponent/loadChildrenfor feature routes. - •Interceptors — Functional interceptors (
HttpInterceptorFn) for auth tokens, error handling, logging. - •Guards — Functional guards (
CanActivateFn) for route protection. - •Resolver — Functional resolvers for pre-fetching route data.
- •Adapter pattern — Wrap third-party libraries behind Angular services.
Anti-Patterns
NEVER do these:
- •NgModules for new code — Use standalone components, directives, pipes.
- •Manual
.subscribe()in components for template data — UseAsyncPipeortoSignal(). - •Business logic in components — Extract to services.
- •
anytype — Use proper TypeScript types. Never suppress with@ts-ignore. - •NgRx/NGXS in new projects — Use signals + facade services. Only use a state library if the project already has one.
- •New barrel files (
index.ts) — Direct imports only. Barrel files cause tree-shaking issues. If project already uses them, follow existing convention. - •Relative imports across features — Use TypeScript path aliases (
@app/core,@app/shared). - •
setTimeout/setIntervalfor Angular state — Use signals, RxJS timers, orafterNextRender. - •Component inheritance — Use composition (services, directives, content projection).
- •Untyped forms — Always use typed reactive forms.
- •
$suffix on signals — Reserve$exclusively for Observables.
Error Handling
- •Global error handler via
ErrorHandler— catch unhandled exceptions. - •HTTP errors via functional interceptor — centralized response error handling.
- •Show user-facing errors through a notification service (toast/snackbar).
- •Log errors to a backend logging endpoint in production.
- •Never swallow errors silently in
catchError— always log or re-throw. - •Use
retry/retryWhenfor transient HTTP failures.
See reference/examples.md for error interceptor and global error handler patterns.
Spring Boot Integration
All new Angular apps are embedded in Spring Boot. Apply the Java skill conventions on the backend side.
Build Integration
- •Use
gradle-node-pluginwired intoprocessResourcesto build Angular as part of the Spring Boot build. - •Angular build output goes to
build/resources/main/static. - •Single deployable artifact: one Spring Boot JAR serves both API and SPA.
Backend Rules (from Java skill)
- •Hexagonal architecture on the backend.
- •Constructor injection exclusively (Java side).
- •No Lombok — ever.
- •Records for DTOs.
- •Spring Cloud Config Server for externalized config.
- •SpringDoc OpenAPI + Swagger UI mandatory.
SPA Forwarding Controller
A forwarding controller must serve index.html for all SPA routes. Regex excludes /api, /v3, /swagger-ui, /actuator, /public, /error.
CORS
CORS configuration only active under local Spring profile. Production serves both frontend and API from the same origin — no CORS needed.
Externalized Configuration
- •Angular fetches runtime config from
/api/configat bootstrap viaAPP_INITIALIZER. - •Backend serves config values from Spring Cloud Config Server — single source of truth.
- •Never hardcode environment-specific values (API URLs, feature flags) in Angular source.
- •Use an
InjectionToken<AppConfig>to inject config throughout the app. - •Use
@RefreshScopeon the config bean so/actuator/refreshreloads values from Config Server. - •Angular picks up refreshed config on next page load — no polling or push mechanism needed.
API Communication
- •Use Angular
HttpClientwith a base URL from injectedAppConfig. - •Define TypeScript interfaces/types matching backend DTOs (Records on backend).
- •Use functional interceptors for auth tokens and error handling.
See reference/examples.md for build.gradle config, forwarding controller, and CORS setup.
Components & Change Detection
- •
OnPushon all new components. SetchangeDetection: ChangeDetectionStrategy.OnPush. - •Signal inputs (
input(),input.required()) over@Input()decorator for new code. - •Signal outputs (
output()) over@Output()+EventEmitterfor new code. - •
@if,@for,@switch— Use built-in control flow (Angular 17+) for new code. Existing codebases: follow established template style; don't mass-migrate. - •
@defer— Lazy-load heavy components in templates. - •Content projection (
ng-content,@ContentChild) over component inheritance. - •
afterNextRender/afterRenderfor DOM manipulation instead ofngAfterViewInithacks.
Reactivity & State
- •Signals for component/service state.
signal()for writable state,computed()for derived values. - •Facade services — One per feature. Encapsulate state as private signals, expose public read-only signals and computed values.
- •
effect()— Use sparingly. Prefercomputed(). Effects are for side effects (logging, analytics, localStorage sync). - •
toSignal()/toObservable()— Bridge between signals and RxJS when needed. - •No NgRx/NGXS for new projects. Signals + facade services cover most state needs.
See reference/examples.md for facade service pattern with signals.
Routing
- •Standalone route configs. Use
Routesarrays in<feature>.routes.tsfiles. - •Lazy loading.
loadComponentfor single routes,loadChildrenfor feature route groups. - •Functional guards and resolvers. Use
CanActivateFn,ResolveFnwithinject(). - •Route params via signals. Use
input()withwithComponentInputBinding()inapp.config.ts. - •Nested routes for feature layouts with
<router-outlet>.
See reference/examples.md for route configuration and lazy loading patterns.
Forms
- •Typed reactive forms only.
FormControl<string>,FormGroup,FormArray. - •No template-driven forms in new code.
- •Custom validators as pure functions returning
ValidatorFn. - •Error display — Reusable error message component/directive.
- •Form submission — Disable submit button while pending. Show validation on blur or submit.
Documentation
- •All public services, components, and interfaces must have JSDoc/TSDoc.
- •Document
@Input()/input()and@Output()/output()with descriptions. - •README in each feature folder for complex features.
- •API contracts documented via SpringDoc OpenAPI on the backend.
Dependencies
- •Check
package.jsonbefore adding any dependency. - •UI library: Check project first. Prefer Angular Material if none present.
- •Linting:
@angular-eslintonly. No separate Prettier. - •State: Signals + services. No NgRx/NGXS unless already in project.
- •HTTP: Angular
HttpClientonly. No axios. - •Run
npm auditafter adding new dependencies. - •Keep Angular, Angular CLI, and Angular Material versions aligned.
Performance Considerations
- •
OnPushchange detection on all components. - •Lazy loading — Routes,
@deferblocks, dynamic imports. - •
trackByin@forloops — Usetrackexpression to minimize DOM churn. - •Pure pipes over method calls in templates.
- •
runOutsideAngularfor non-UI event listeners (scroll, resize). - •Preloading strategies —
PreloadAllModulesor custom strategies for lazy routes. - •Bundle analysis —
npx ng build --stats-json+webpack-bundle-analyzer. - •Image optimization — Use
NgOptimizedImagedirective.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
NG0100: ExpressionChangedAfterItHasBeenChecked | Value changed between change detection cycles | Use signals, or move logic to afterNextRender |
NG0200: Circular dependency in DI | Service A injects B, B injects A | Refactor with events or mediator service |
NG0300: Multiple components match selector | Duplicate selectors | Use unique app- prefixed selectors |
NG0303: Can't bind to 'X' since it isn't a known property | Missing import in standalone component | Add component/directive to imports array |
NullInjectorError: No provider for X | Service not provided | Add to providers in component or app.config.ts |
NG05104: Signal is read-only | Trying to .set() on computed() | Use signal() for writable state |
404 on page refresh (SPA) | Server doesn't forward SPA routes | Add forwarding controller (see Spring Boot Integration) |
Build out of memory | Large app, insufficient heap | node --max_old_space_size=8192 node_modules/.bin/ng build |
| CORS errors in dev | Missing CORS config | Enable CORS under local Spring profile only |
Design Principles
- •SOLID — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion.
- •DRY — Don't repeat yourself, but don't extract prematurely.
- •KISS — Straightforward solutions over clever abstractions.
- •YAGNI — Don't build for hypothetical requirements.
- •Composition over inheritance — Services, directives, content projection.
- •Separation of concerns — Components handle UI, services handle logic and state.
- •Unidirectional data flow — Data flows down via inputs, events flow up via outputs.
- •Single source of truth — One facade service owns each piece of state.
Checklist Before Completion
- • Lint passes:
npm run lint(ornpx ng lint) - • Build succeeds:
npm run build(ornpx ng build) - • All tests pass:
npm test - • E2E passes (if present):
npm run e2e - • Backend checks pass:
./gradlew compileJava,./gradlew test - • Full Spring Boot build:
./gradlew build - •
strictTemplates: trueintsconfig.json - • All components use
OnPushchange detection - • All components are standalone (no NgModules)
- • Built-in control flow (
@if,@for) used — no structural directives - • Signals for synchronous state, Observables for async streams
- • No manual
.subscribe()for template data - • No
anytypes - • No Lombok on backend
- • Facade services used for feature state (no NgRx/NGXS)
- • TypeScript interfaces match backend DTOs
- • Config externalized via
/api/config+APP_INITIALIZER(no hardcoded env values) - • No secrets in source or logs
- •
npm auditclean - • Backend endpoints have SpringDoc OpenAPI annotations