Skip to content

Angular + PrimeNG Standards

The rules live in committed config, not in this page. An existing app adopts a row at its next Angular major.

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.

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 HttpContext token, 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’s providers so 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.

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 dt property 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.

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 matching ms- / me- / ps- / pe- / text-start utilities. No left / right outside a [dir] block.
  • RTL is definition-of-done. A UI task is not finished until it has been viewed in Arabic.
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.jsonstrict, 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.

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.

👤 Owner: Kenana Reda🗓 Last reviewed: 2026-07-25

اقرأ هذه الصفحة بالعربية