Conventions for Angular frontends (v20+, signals, standalone, zoneless/OnPush) built with PrimeNG components and Tailwind CSS that talk to a versioned ASP.NET Core API. Use when writing, placing or reviewing Angular code, e.g. a feature, component, page layout, PrimeNG table/form/dialog, route, API service, interceptor, or a spec file, or when wiring the frontend to a backend endpoint.
Resources
1Install
npx skillscat add gitquuu/qugents/angular-frontend Install via the SkillsCat registry.
Angular frontend
Targets current Angular (v22: OnPush by default, zoneless, Signal Forms and resource/httpResource stable, Vitest as test runner) with PrimeNG 22 and Tailwind CSS 4. Check @angular/core, primeng and tailwindcss in package.json first. If the project is on an older version or uses other conventions (NgModules, .component.ts file names, NgRx, Karma/Jasmine), follow the project and say so. Do not migrate unasked.
Principles
- Thin components. A component presents and reacts. Data access, validation rules and transformations live in services or plain functions. This mirrors thin controllers on the backend.
- Feature-first folders. Group by feature, not by type. No top-level
components/,services/folders. - Signals for state.
signal,computed,input(),output(),model(). RxJS where streams are the natural fit (HTTP writes, polling, websockets); bridge withtoSignal/rxResource. - One API service per backend resource. It owns URLs and DTO types. Components never build URLs or call
HttpClientdirectly. - The backend is the authority. Client-side validation and role checks are for UX only. The API validates and authorizes again.
- PrimeNG for controls, Tailwind for layout. UI controls come from PrimeNG; spacing, layout, typography and small visual tweaks are Tailwind utilities. No other component library or CSS framework, and almost no hand-written CSS.
Structure
src/
main.ts
app/
app.ts app.html app.config.ts app.routes.ts
core/ app-wide singletons: auth, interceptors, error handling, layout
auth/ auth-store.ts auth-interceptor.ts auth-guard.ts
http/ api-url.ts problem-details.ts error-interceptor.ts
shared/ reusable presentational UI and pure helpers (no feature logic)
features/
products/
products.routes.ts lazy routes for the feature
products-api.ts API service: one per backend controller/resource
product.ts DTO types mirroring the C# DTOs
product-list/ page (container) component
product-list.ts product-list.html product-list.scss product-list.spec.ts
product-edit/
product-card/ presentational component
environments/
styles.css Tailwind + PrimeNG layer order (plain CSS)Naming (Angular v20+ style guide): hyphenated file names, file named after its main class, no type suffix: product-list.ts exports ProductList. Tests sit beside the code as *.spec.ts. Avoid utils.ts, helpers.ts, common.ts.
Components
- Standalone (default). Do not set
standalone: trueexplicitly in v19+. - Change detection: leave the default (OnPush in v22). Never opt into
Eagerfor new code. - Dependencies with
inject(), not constructor parameters. - Order: injected dependencies, inputs/outputs/queries, state signals, computed, methods.
readonlyoninput(),output(),model()and queries.protectedon members used only by the template.- Templates: built-in control flow (
@if,@for (item of items(); track item.id),@switch,@defer).[class.x]/[style.x], notNgClass/NgStyle. Keep expressions simple; move logic intocomputed. - Event handlers named for the action (
saveProduct()), not the trigger (onClick()). - Container vs presentational: route-level (container) components load data and call services. Presentational components get data via
input()and emit viaoutput(), and inject nothing feature-specific.
UI: PrimeNG + Tailwind CSS
Packages: primeng, @primeuix/themes, @angular/cdk, tailwindcss, @tailwindcss/postcss, postcss, tailwindcss-primeui. Optional: primeicons for pi pi-* icons.
License (check before adding or upgrading): PrimeNG 22+ needs a PrimeUI license key in providePrimeNG({ license }), otherwise every page shows an "invalid license" banner. The Community License is free for organizations under USD 1M revenue, under 5 developers and under 10 employees (renewed yearly); others need a paid per-developer Commercial license. PrimeNG 21 and older stay MIT but only support Angular 21. Never hide the banner with CSS or code; configure the key. The key is public by design and lives in environment.ts.
Setup (full files in the templates):
.postcssrc.jsonwith the@tailwindcss/postcssplugin.src/styles.css(plain CSS; Tailwind v4 does not run through Sass):@layer theme, base, primeng, components, utilities;, then@import "tailwindcss";and@import "tailwindcss-primeui";, plus@custom-variant dark (&:where(.app-dark, .app-dark *));.providePrimeNG({ theme: { preset: Aura, options: { darkModeSelector: '.app-dark', cssLayer: { name: 'primeng', order: 'theme, base, primeng' } } }, license })inapp.config.ts. WithoutcssLayer, PrimeNG styles are unlayered and beat every Tailwind utility.MessageServiceandConfirmationServiceprovided once inapp.config.ts;<p-toast />once inapp.html.
Using components:
- Import standalone classes per component:
Buttonfromprimeng/button,InputText,InputNumber,Select,DatePicker,Dialog,ConfirmDialog,Toast,Tag;TableModuleforp-table. - Templates use template refs:
<ng-template #header>,#body let-row,#emptymessage. Not the oldpTemplate="...". - Events are PrimeNG outputs, e.g.
(onClick)onp-button. - Pick the PrimeNG component first (table, select, date picker, dialog, toast, confirm, tag, skeleton, progress bar) before building your own.
- Styling order: component props (
severity,size,outlined,text) → Tailwind classes viaclass/styleClass→pt(pass-through) for inner elements → theme preset (definePreset) for brand-wide changes. Never::ng-deep,!importantor overriding.p-*classes in CSS. - Colors: prefer the theme-aware
primary-*andsurface-*utilities fromtailwindcss-primeuiover raw palette colors, so dark mode and the preset keep working. - Feedback: success and API errors as toasts (
MessageService.add), destructive actions behindConfirmationService.confirm, loading states with[loading]onp-table/p-buttonorp-skeleton. - Lists:
p-tablewithdataKey; server paging/sorting via[lazy]="true"and(onLazyLoad)mapped to the API's paging parameters. - Accessibility: every input has a
<label for>/inputIdpair; icon-only buttons getariaLabel.
Talking to the .NET API
- Base URL from
environment.apiUrlplus the version segment:${apiUrl}/api/v1/products. Keep it in one place (core/http/api-url.ts). - DTO types mirror the C# DTOs (JSON is camelCase). When a backend DTO changes, update the TS type in the same task. Dates arrive as ISO strings; convert at the edge if needed.
- Reads:
httpResource()(orrxResource) in the component or a store service. It exposesvalue(),isLoading(),error()andreload(). - Writes: methods on the API service returning
Observable<T>. The component subscribes once, or usesfirstValueFromin anasynchandler. - Errors: the API returns
ProblemDetails({ status, title, errors? }). A functional error interceptor maps them to a typedApiError. 400 witherrorsmaps to field errors on the form; 401 triggers refresh or login; 403 shows "not allowed"; 404/409 are shown in context. Never swallow errors silently. - Auth (backend:
dotnet-identity-jwt):AuthStorekeeps the JWT access token in memory only (signals) and exposesisLoggedIn,userId,roles,hasRole(). The refresh token is an HttpOnly cookie the browser sends to/api/v1/auth/*, so login/refresh/logout usewithCredentials: true.provideAppInitializercallsrefresh()once to restore the session after a reload. A functionalHttpInterceptorFnadds the bearer token and, on 401 outside/auth/, waits for one shared refresh and retries. Guards are functional (CanActivateFn) and check login/roles for UX only. Never put tokens inlocalStorage/sessionStorage. - Background jobs (202 +
jobId): the API service returns thejobId; a small polling helper (timer+switchMapto the jobs endpoint,takeWhileuntil done/failed) drives a status signal in the component.
Register HTTP in app.config.ts: provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])). In v22 fetch is the default backend.
Forms
- v22+: Signal Forms (
form()from@angular/forms/signals,[formField]in the template, validators likerequired,email,minLengthin the schema callback).[formField]works onpInputTextand on PrimeNG form components such asp-inputnumber,p-select,p-datepicker. - Older versions or existing reactive forms: typed reactive forms (
FormBuilder.nonNullable). Do not mix both in one form. - Mirror the backend's DataAnnotations (required, max length, ranges) for UX, and map server-side 400 field errors back onto the form.
Routing
- Lazy per feature:
{ path: 'products', loadChildren: () => import('./features/products/products.routes') }, andloadComponentfor single pages. - Route params via
input()withwithComponentInputBinding(). - Guards and resolvers are functions.
Security
- Never call
bypassSecurityTrust*on data that came from users or the API. Never bind untrusted HTML with[innerHTML]unless sanitized. - No secrets in
environment.ts; it ships to the browser. (The PrimeUI license key is public by design and belongs there.) - Do not log tokens or personal data to the console.
Testing
- Vitest (default from v21) with
TestBed. Follow the project's runner if it still uses Karma/Jasmine. - Arrange/Act/Assert, as in the backend tests.
- HTTP:
provideHttpClient()+provideHttpClientTesting()andHttpTestingController(expectOne,flush,verify). - Test API services (URL, method, body, error mapping), stores/services with logic, and container components' behavior. Presentational components: only when they hold logic.
- Component specs that render PrimeNG need
providePrimeNG({ theme: 'none' })plusMessageService/ConfirmationServicein the TestBed providers. Flush pendinghttpResourcerequests withHttpTestingControllerbeforeawait fixture.whenStable().
Commands
# one-time setup in a new app
npm install primeng @primeuix/themes @angular/cdk tailwindcss @tailwindcss/postcss postcss tailwindcss-primeui
npm ci
npx ng build
npx ng test --watch=false
npx ng serve # https://localhost:4200 (HTTPS needed for the refresh cookie, see dotnet-new-solution)
npx ng lint # if configuredDevelopment URLs: Angular on https://localhost:4200, API on https://localhost:5001 (environment.apiUrl).
References
references/angular-templates.md: skeletons for PrimeNG + Tailwind setup (app.config.ts,styles.css,.postcssrc.json), an API service, DTO types, a list page withp-table+ confirm + toast, a presentational component, an edit form with Signal Forms on PrimeNG inputs, routes, interceptors, ProblemDetails mapping, job polling, and specs. Read it before writing new frontend code.