Angular Frontend Standard
*Status:* v1.1 authoritative · Owner: Frontend team (CTO sign-off) · Last reviewed: 2026-07-03 · Review cadence: quarterly
The standard for FT frontend teams (Angular / TypeScript; reference repo angular-ui).
It is self-contained — a frontend engineer needs only this page (plus the shared FE↔BE contract).
Backend engineers have their own Java standard.
This page is the
visual form of the authoritative ft-angular-standards skill; on conflict with any other Angular
standard, this wins.
|
Two standards, two teams, one platform. They diverge only where the stacks genuinely differ
(e.g. the |
C1 · Folders = the catalog of the app ⭐
Same idea as the backend’s packages: the folder tree is how you catalog and find code. A new file
goes into its feature folder, named by its role, and is exposed through a barrel (index.ts /
public-api.ts). Never dump a file at the app root, and never inline helper logic in a component.
| ✅ Do — feature first, role second | ❌ Don’t |
|---|---|
|
|
Workspace: a shared ft-common library + apps. Import across features only through the barrel.
Selector prefixes: ft-common (library) / app (apps).
C2 · Naming — role suffix, no Hungarian
| Rule | Angular (TS) |
|---|---|
Role suffix |
|
Enums |
end with |
Interfaces |
keep the |
Variables/methods |
|
Forbidden |
❌ Hungarian notation |
|
"Hungarian notation" = baking a variable’s type or scope into its name via a prefix
( |
Class suffixes are enforced by @angular-eslint. The I-prefix is the one intentional
divergence from the Java standard (which drops it).
|
Both the I-prefix and the …Enum suffix are deliberate FT conventions for greppability and
uniform cataloguing — not a claim that either is the official Angular/TS idiom (the Angular style guide
suffixes neither). Where idiom and convention diverge, the convention wins on these pages.
|
C3 · Immutability by default
| ✅ readonly model | ❌ in-place mutation |
|---|---|
|
|
NgRx state is updated by spread; use as const for literals. Mutable types only where Angular requires
them (reactive form models).
C4 · Constructor injection · OnPush
Inject through the constructor (or inject(), consistently with the repo). Components are
ChangeDetectionStrategy.OnPush; a BaseComponent provides destroy$ + the takeUntil pattern; unwrap
observables with the async pipe.
C5 · Logging ⭐
Log through the app logger, never raw console. Include the device context you already have as
key=value so a frontend log greps like a backend one.
| ✅ Do | ❌ Don’t |
|---|---|
|
|
Levels: error for failed ops surfaced to the user, warn for degraded/retried, info for notable
user actions, debug for dev detail (silenced in prod). ❌ No per-render / per-keystroke logging; ❌ no stray
console.log in committed code — use the app logger.
|
C6 · Architecture
| Area | Standard |
|---|---|
Components |
NgModule (not standalone), |
State |
RxJS (no signals yet) + NgRx; errors surface through effects → toastr |
Structure |
|
NgModule (not standalone), RxJS-only (no signals), and the BaseComponent + destroy$/takeUntil
pattern are a deliberate choice tied to the current Angular major — not the framework’s current default
(standalone, signals, and takeUntilDestroyed/DestroyRef are). Revisit on the next major upgrade; until
then this is the norm, so don’t introduce the newer patterns piecemeal.
|
C7 · Style — current permissive norm (keep, do NOT harden)
| Config | Setting (keep as-is) |
|---|---|
|
printWidth 120, single quotes, semis, trailingComma es5, |
|
2 spaces |
|
the existing rules |
Do not tighten tsconfig / eslint strictness as part of unrelated work — the permissive
norm is deliberate.
|
C8 · Tests
-
Specs next to the code (
*.spec.ts); name behaviourally (should … when …); group related cases. -
Test components / services / effects with the Angular testing utilities; mock HTTP at the
ApiServiceboundary. -
Coverage: new/changed code ≥ 80% (diff coverage); no global gate. Cover behaviour (reducers as pure functions, effects with a mocked actions stream), not lines.
-
Spec first: a new feature (architecture-led) needs an approved SDD before implementation; a bug fix is TDD-led (failing test first).
C9 · Constants & enums
-
❌ No magic numbers/strings — name them; group in a
*.const.tsor anas constobject. Prefer a stringenum(…Enum) or aconstunion over scattered literals. -
Route paths, NgRx action types, storage keys, error codes — single source of truth as constants, never re-typed inline.
-
Build/config values live in Angular
environment.*, not hard-coded constants.
C10 · Caching & client state
-
NgRx is the client cache — read from selectors; don’t re-fetch what’s already in the store. An HTTP call the store can answer is a bug.
-
Cache HTTP responses deliberately (interceptor / service) with a clear invalidation trigger; ❌ no unbounded caches.
-
Don’t duplicate server state in component fields — derive from selectors via the
asyncpipe.
C11 · External libraries
-
Prefer Angular / RxJS / the existing stack first; ❌ no second library for a job an existing one does (no extra date / HTTP / state libs). Mind bundle size.
-
A new dependency needs approval; pin exact versions (lockfile). Respect licenses; avoid abandoned packages.
-
npm auditis part of "done" when deps change — upgrade when safe, flag HIGH/CRITICAL you can’t bump; ❌ never--force.
C12 · Component & code discipline — code that stays readable ⭐
Review norms, not CI gates: exceeding one needs a stated reason in the PR, not silence.
| Threshold | Rule |
|---|---|
Component class ≤ 200 lines |
larger → split (child components, co-located |
≤ 7 `@Input`s |
more → group into a model object |
Method ≤ 40 lines · nesting ≤ 3 |
guard clauses / early return — same norm as the backend |
-
Smart/dumb split enforced (C6): dumb components take inputs and emit outputs — no store access, no HTTP.
-
Rule of Three: 2 duplicates → leave + mark
// DUPE:; the 3rd → extract (shared helper /ft-common). Look-alike blocks changing for different reasons are not duplicates. -
YAGNI applies to capabilities, never to quality — no speculative `@Input`s or generic wrappers without a second consumer; never an excuse to skip tests/error handling.
-
Comments say WHY, not what; ❌ no commented-out code, no dead exports in barrels.
-
Simplicity first (KISS): the simplest code that passes the tests and reveals intent — arrow functions and small pure helpers over stateless classes; the simplest RxJS chain that works (2–3 well-named operators beat a clever 7-operator one-liner); ❌ no abstractions/patterns (wrapper services, generic base classes) without a present second consumer; ❌ no clever one-liners that need a comment to decode; no premature optimization — measure first.
C13 · RxJS discipline
-
❌ No nested
subscribe— compose with a flattening operator, chosen by semantics:
Operator |
When |
|
only the latest matters — typeahead, route params, reload |
|
ignore re-triggers while busy — form submit, login click (double-submit guard) |
|
every event matters, in order — queued writes |
|
independent parallel work (rare; justify the concurrency) |
-
catchErrorgoes INSIDE the inner stream of an effect — on the flattened HTTP observable, returning a failure action. On the outer stream it kills the effect after the first error (it silently stops dispatching forever). -
❌ No manual
subscribein components where theasyncpipe can unwrap (C4/C6); a manual subscription always pairs withtakeUntil(destroy$). -
Multicast expensive shared streams deliberately —
shareReplay({ bufferSize: 1, refCount: true })— instead of re-triggering the source per subscriber.
C14 · Templates & rendering performance
-
trackByon every*ngForover changing data — without it OnPush re-creates the DOM per emission. -
❌ No function calls in template expressions (
{{ compute(x) }}) — they run every change-detection cycle; precompute in the component/selector or use a pure pipe. Derive view state in memoized NgRx selectors, not getters. -
Lazy-load feature routes (
loadChildren) — a new feature module ships lazy by default. -
Heavy lists: CDK virtual scroll before pagination hacks; images sized/lazy.
C15 · Security baseline (frontend)
| Violations here are blocking review findings, not style notes. |
-
❌ No
[innerHTML]/bypassSecurityTrustwith user- or device-supplied content* — Angular sanitizes interpolation by default; don’t opt out. A genuine rich-HTML need goes through the sanitizer and a review. -
Secrets never reach the bundle:
environment.*.tsholds endpoints and flags — ❌ no API keys/credentials (everything in the bundle is public). ❌ Never log tokens/credentials via the app logger (C5). -
❌ No
eval/new Function/ dynamic<script>injection. -
External links with
target="_blank"carryrel="noopener noreferrer". -
The auth/session storage scheme follows the repo’s established mechanism — moving tokens between localStorage/cookie/memory is a CTO-level architecture decision, not a drive-by "fix".
✅ Review checklist
-
New file in a feature folder by role, exported via the barrel — not the app root
-
readonly / immutable models & NgRx state (spread, no in-place mutation)
-
Constructor injection; components OnPush;
destroy$+takeUntil -
FE logs via the app logger as
key=value(e.g.deviceId); right level, no per-render noise / strayconsole.log -
Naming:
.component.ts/.service.ts…; interfacesI-prefixed;camelCasevars, verb methods; enums end…Enum -
No magic numbers/strings; constants/
as const/enum; reads from NgRx store (no needless re-fetch) -
New code ≥ 80% diff coverage; new dependency justified + pinned +
npm audit-clean; no duplicate-purpose lib -
Style left at the current permissive norm
-
FE↔BE contract matches the backend exactly
-
Size discipline: component ≤ 200 lines / ≤ 7 inputs; methods ≤ 40 lines, nesting ≤ 3; dumb components have no store/HTTP
-
Simplicity: pure helpers over stateless classes; simplest RxJS chain that works; no abstractions without a second consumer; no clever one-liners
-
RxJS: no nested subscribes; right flattening operator (
exhaustMapsubmits /switchMaplatest-wins /concatMapordered);catchErrorinside the effect’s inner stream -
Templates:
trackByon everyngFor; *no function calls in template expressions; new features lazy-loaded -
Security: no
[innerHTML]/bypassSecurityTrust*on user content; no secrets inenvironment.*/bundle; noeval;rel="noopener"on_blank
| Next → the shared FE↔BE contract · the Java standard. |