Angular + PrimeNG Standards
The rules live in committed config, not in this page. An existing app adopts a row at its next Angular major.
Defaults
Section titled “Defaults”One default per row. Deviating needs an ADR in that repo.
| Decision | Default | Escape hatch |
|---|---|---|
| Components | Standalone, OnPush, input() / output(), inject() |
— |
| Reactivity | Signals. RxJS only for real streams (events, cancellation, debounce) | — |
| State | A signal store class (see below) | @ngrx/signals for normalised entity collections — a new choice, via ADR |
| Component library | PrimeNG, one per app, chosen at kickoff | An app already on another library keeps it. Never two in one app |
| Styling | Tailwind + the library’s design tokens | — |
| Change detection | Zoneless for new apps — no zone.js |
An existing zone-based app stays zone-based until a deliberate migration |
| Rendering | Client-side SPA. No SSR, no hydration | SSG prerendering for a public, SEO-bearing surface, decided at kickoff |
| Routing | Every feature route lazy. Authorization guards use canMatch |
— |
| Unit tests | Vitest, via the CLI @angular/build:unit-test builder |
— |
| E2E | Playwright, critical journeys only | — |
| Forms | Reactive Forms | — |
canMatch, not canActivate — a denied route must not download its chunk.
Data flow
Section titled “Data flow”ui/ component → routed page → signal store → *-api.ts → interceptors → API
- Components never inject
HttpClient. The api file wraps it and maps DTOs; the store holds the logic. - Errors become a typed result union at the api boundary — never parse a backend error string for control flow.
- Cross-cutting headers are interceptors. A per-request opt-out uses an
HttpContexttoken, never URL string matching.
A plain @Injectable() class: private signal() fields, public .asReadonly() and computed() views.
- Page state: no
providedIn; list the store in the routed page’sprovidersso it is created and destroyed with the page. - Cross-feature state:
providedIn: 'root'.
The lifetime detail is the part people get wrong — a root-singleton store quietly serves stale state the second time a user opens the page.
Component library
Section titled “Component library”One library per app, chosen at kickoff, never mixed. A second library “just for the datatable” is never approved.
- Theme through design tokens — a preset for client branding, the scoped
dtproperty for a one-off instance. - Never fork component styles. No copied SCSS, no
::ng-deep. If a token can’t express it, use documented passthrough (pt) for structural cases and record the reason.
i18n & RTL
Section titled “i18n & RTL”Bilingual or RTL is a kickoff decision, not a retrofit. If the client is Arabic-facing, the app is RTL-first from commit one.
- No hard-coded user-visible text, ever — every string goes through the translation layer.
- Logical CSS properties only:
margin-inline-start,padding-inline-end,inset-inline-start,text-align: start, and the matchingms-/me-/ps-/pe-/text-startutilities. Noleft/rightoutside a[dir]block. - RTL is definition-of-done. A UI task is not finished until it has been viewed in Arabic.
What CI enforces
Section titled “What CI enforces”| Concern | Where it lives | Enforcement |
|---|---|---|
| Lint | eslint.config.js (flat config, angular-eslint + template rules) |
npm run lint fails the PR |
| Formatting | .prettierrc |
prettier --check |
| Type + template strictness | tsconfig.json — strict, strictTemplates, strictInjectionParameters |
Build fails |
| Bundle size | angular.json budgets |
Build fails. Raising a budget is a PR with a reason |
Every lint rule is error. There is no warn tier — a rule set to warn is a rule nobody runs, and --quiet hides it entirely. That includes the template accessibility rules.
Testing
Section titled “Testing”Unit tests run on Vitest through ng test; E2E on Playwright. See Testing strategy for the coverage gate, the CI lanes, and the flaky-test policy.
See also: Examples · API Design · Testing · Branching & PRs.