dotCMS Angular Standards (core-web)
Precedence. This skill overrides the vendored angular-developer skill for all work in this
repository. That skill is upstream, generic Angular guidance: it assumes ng CLI, angular.json,
Karma, and a greenfield app. None of those apply here. Where the two disagree, this skill wins.
Use angular-developer only for framework concepts it explains better (signals semantics, DI
resolution, routing APIs) — never for its tooling, project-creation, or standalone/changeDetection
advice.
That skill is vendored byte-for-byte from upstream Angular and must never be hand-edited — new dotCMS
rules belong here instead. See
.agents/skills/angular-developer/PROVENANCE.md
for its upstream source and re-sync procedure.
Source of truth
docs/frontend/ANGULAR_STANDARDS.md is the single
source of truth for Angular work in this repository. Read it before writing or reviewing Angular
code.
It has siblings — load the one your task needs, or
docs/frontend/README.md (the index) if you are unsure:
| Task |
Doc |
| Component structure, file layout, data flow |
COMPONENT_ARCHITECTURE.md |
Feature state — NgRx Signal Store, rxMethod, patchState |
STATE_MANAGEMENT.md |
| Tailwind, PrimeNG theme, BEM, SCSS |
STYLING_STANDARDS.md |
Strict types, as const, # private |
TYPESCRIPT_STANDARDS.md |
Writing tests — Spectator, Jest, byTestId |
TESTING_FRONTEND.md |
| Reviewing tests — violation checklist |
TESTING_REVIEW_RULES.md |
| GlobalStore breadcrumb trail |
BREADCRUMBS.md |
This file is deliberately not a copy of it. It carries only:
- the trigger (so it fires automatically when you touch frontend code),
- the precedence declaration over the vendored
angular-developer skill, and
- the rules where upstream actively tells you to do the wrong thing here, so the correction is in
front of you at the moment you would otherwise follow upstream.
Everything else — full rationale, examples, and the rules upstream already gets right — lives in the
doc. If this file and the doc ever disagree, the doc wins and this file is the bug. When a
standard changes, change it in the doc; only touch this file if the change is one upstream contradicts.
Workspace facts
- Nx monorepo rooted at
core-web/. There is no angular.json — the workspace is driven by nx.json.
- Angular 22.x, PrimeNG 21.x, Tailwind 4.x, NgRx Signals 21.x, Jest 30, TypeScript 6, Node 22.22.3+.
- Package manager: pnpm (see
packageManager in core-web/package.json).
core-web/package.json is the source of truth for versions. Read it; never hardcode or assume a patch version.
Non-negotiables
1. Never write standalone: true
Standalone is the Angular default. The property is forbidden in decorators here. Remove it when you
see it in a file you are already editing.
2. Never set changeDetection on a new component
OnPush is the framework default as of Angular v22
(docs), so an
explicit ChangeDetectionStrategy.OnPush is redundant noise.
Components explicitly marked ChangeDetectionStrategy.Eager (the opt-in eager mode, renamed from
Default in v22) opted in deliberately — leave them alone. Do not convert Eager to OnPush
when touching a file for unrelated work.
3. Build, test, and serve through pnpm nx — never ng
There is no Angular CLI workspace here. Never yarn, never npm.
| Task |
Command (run from core-web/) |
| Install deps |
pnpm install |
| Serve the UI |
pnpm nx run dotcms-ui:serve |
| Test a project |
pnpm nx run dotcms-ui:test |
| Build a project |
pnpm nx build dotcms-ui |
| Scaffold code |
Nx generators (pnpm nx g …) — see the nx-generate skill |
4. Testing is Jest 30 + @openng/spectator
- Never
@ngneat/spectator — the workspace has fully migrated (0 occurrences remain). Never Karma, never Vitest.
- Select with
byTestId and put data-testid on the elements you assert against.
- Set inputs with
spectator.setInput(). Never assign to component properties directly.
- Mock stores in
componentProviders, not providers.
5. Three files per component — .ts + .html + .scss
Inline template: and inline styles: in the decorator are forbidden. Reference the files with
templateUrl and styleUrls (plural — matching docs/frontend/ANGULAR_STANDARDS.md and the
dominant convention in the workspace).
6. Reuse before creating
Before creating a new component, in this order:
- Check dotCMS's own components —
core-web/libs/ui (@dotcms/ui) and the existing feature libs.
- Check PrimeNG.
- Only then create a new component — and say in the PR why neither of the above fit.
7. Every component that renders data handles all four states
loading, empty, error, loaded — explicitly. Never leave a blank render path. If a
template has no branch for a state, that state is a bug.
8. Error handling is mandatory
No silent failures. No unguarded .subscribe(). No empty catchError. Handle the error in the service
with catchError, surface a user-facing error state in the component, and log the diagnostic detail.
9. Icons are Material Symbols
<span class="material-symbols-outlined">drag_indicator</span>
The icon name goes in the element's text content, not in the class. Fonts are self-hosted via
core-web/libs/dotcms-scss/shared/_material-symbols-outlined.scss.
- Existing PrimeIcons (
pi pi-*) stay as they are — do not mass-migrate them.
- PrimeNG's internal icons are a theming concern and out of scope.
- Do not use the deprecated
dot-icon component.
10. dotCMS-specific naming
These differ from stock Angular convention, so upstream will not tell you about them:
| Rule |
Do |
Don't |
| Signals |
$mySignal ($ prefix) |
mySignal, mySignal$ |
| Observables |
myObservable$ ($ suffix) |
$myObservable |
| Selectors |
dot- prefix |
anything else |
Covered in full by the doc — read it, do not rely on this file
docs/frontend/ANGULAR_STANDARDS.md is authoritative for everything below. Upstream Angular guidance
already agrees with us on these, so they are not restated here and this file must not grow a second
copy of them:
- Subscription teardown —
inject(DestroyRef) + takeUntilDestroyed(); the legacy destroy$
pattern is not for new work and is not to be mass-migrated.
- Signal Forms first — new forms use
@angular/forms/signals; existing Reactive Forms stay.
- TypeScript 6 — the transitional
"ignoreDeprecations": "6.0" flag; new code must not rely on it.
- Standard modern Angular API —
input() / output() over decorators, inject() over constructor
injection, @if / @for (with track) / @switch over *ngIf / *ngFor / *ngSwitch,
[class.x] / [style.x] over ngClass / ngStyle, and host bindings in the host: { … } object
over @HostBinding / @HostListener.
Before you finish
1---2name: dot-ui-angular-standards3description: dotCMS Angular coding standards for the core-web Nx workspace. Use this skill for ANY frontend work under core-web/ — writing or editing a component, service, store, directive, pipe, guard, template, SCSS file, or Jest spec; reviewing a frontend diff; scaffolding new UI; or answering "how do we do X in Angular in this repo". Also trigger whenever any of these appear: `standalone: true`, `changeDetection`, `ChangeDetectionStrategy`, `ng new`/`ng generate`/`ng serve`/`ng build`/`ng test`, `angular.json`, Karma, `@ngneat/spectator`, `npm run`/`yarn` inside core-web, inline `template:` or `styles:` in a decorator, `*ngIf`/`*ngFor`, `ngClass`/`ngStyle`, `@Input()`/`@Output()` decorators, `@HostBinding`/`@HostListener`, `destroy$`/`takeUntil`, constructor injection, `dot-icon`, PrimeIcons, or "add an icon". This skill OVERRIDES the vendored `angular-developer` skill's generic Angular and Angular CLI guidance whenever the work is inside this repository.4---56# dotCMS Angular Standards (core-web)78**Precedence.** This skill overrides the vendored `angular-developer` skill for all work in this9repository. That skill is upstream, generic Angular guidance: it assumes `ng` CLI, `angular.json`,10Karma, and a greenfield app. None of those apply here. Where the two disagree, **this skill wins**.11Use `angular-developer` only for framework concepts it explains better (signals semantics, DI12resolution, routing APIs) — never for its tooling, project-creation, or `standalone`/`changeDetection`13advice.1415That skill is vendored byte-for-byte from upstream Angular and must never be hand-edited — new dotCMS16rules belong here instead. See17[`.agents/skills/angular-developer/PROVENANCE.md`](../../../.agents/skills/angular-developer/PROVENANCE.md)18for its upstream source and re-sync procedure.1920## Source of truth2122> **[`docs/frontend/ANGULAR_STANDARDS.md`](../../../docs/frontend/ANGULAR_STANDARDS.md) is the single23> source of truth for Angular work in this repository. Read it before writing or reviewing Angular24> code.**2526It has siblings — load the one your task needs, or27[`docs/frontend/README.md`](../../../docs/frontend/README.md) (the index) if you are unsure:2829| Task | Doc |30| --- | --- |31| Component structure, file layout, data flow | [COMPONENT_ARCHITECTURE.md](../../../docs/frontend/COMPONENT_ARCHITECTURE.md) |32| Feature state — NgRx Signal Store, `rxMethod`, `patchState` | [STATE_MANAGEMENT.md](../../../docs/frontend/STATE_MANAGEMENT.md) |33| Tailwind, PrimeNG theme, BEM, SCSS | [STYLING_STANDARDS.md](../../../docs/frontend/STYLING_STANDARDS.md) |34| Strict types, `as const`, `#` private | [TYPESCRIPT_STANDARDS.md](../../../docs/frontend/TYPESCRIPT_STANDARDS.md) |35| **Writing** tests — Spectator, Jest, `byTestId` | [TESTING_FRONTEND.md](../../../docs/frontend/TESTING_FRONTEND.md) |36| **Reviewing** tests — violation checklist | [TESTING_REVIEW_RULES.md](../../../docs/frontend/TESTING_REVIEW_RULES.md) |37| GlobalStore breadcrumb trail | [BREADCRUMBS.md](../../../docs/frontend/BREADCRUMBS.md) |3839This file is deliberately **not** a copy of it. It carries only:40411. the **trigger** (so it fires automatically when you touch frontend code),422. the **precedence** declaration over the vendored `angular-developer` skill, and433. the rules where upstream actively tells you to do the **wrong** thing here, so the correction is in44 front of you at the moment you would otherwise follow upstream.4546Everything else — full rationale, examples, and the rules upstream already gets right — lives in the47doc. **If this file and the doc ever disagree, the doc wins and this file is the bug.** When a48standard changes, change it in the doc; only touch this file if the change is one upstream contradicts.4950## Workspace facts5152- Nx monorepo rooted at `core-web/`. There is **no `angular.json`** — the workspace is driven by `nx.json`.53- Angular 22.x, PrimeNG 21.x, Tailwind 4.x, NgRx Signals 21.x, Jest 30, TypeScript 6, Node 22.22.3+.54- Package manager: **pnpm** (see `packageManager` in `core-web/package.json`).55- **`core-web/package.json` is the source of truth for versions.** Read it; never hardcode or assume a patch version.5657## Non-negotiables5859### 1. Never write `standalone: true`6061Standalone is the Angular default. The property is forbidden in decorators here. Remove it when you62see it in a file you are already editing.6364### 2. Never set `changeDetection` on a new component6566`OnPush` is the framework default as of Angular v2267([docs](https://angular.dev/guide/components/advanced-configuration#changedetectionstrategy)), so an68explicit `ChangeDetectionStrategy.OnPush` is redundant noise.6970Components explicitly marked `ChangeDetectionStrategy.Eager` (the opt-in eager mode, renamed from71`Default` in v22) opted in deliberately — **leave them alone.** Do not convert `Eager` to `OnPush`72when touching a file for unrelated work.7374### 3. Build, test, and serve through `pnpm nx` — never `ng`7576There is no Angular CLI workspace here. Never `yarn`, never `npm`.7778| Task | Command (run from `core-web/`) |79| --- | --- |80| Install deps | `pnpm install` |81| Serve the UI | `pnpm nx run dotcms-ui:serve` |82| Test a project | `pnpm nx run dotcms-ui:test` |83| Build a project | `pnpm nx build dotcms-ui` |84| Scaffold code | Nx generators (`pnpm nx g …`) — see the `nx-generate` skill |8586### 4. Testing is Jest 30 + `@openng/spectator`8788- Never `@ngneat/spectator` — the workspace has fully migrated (0 occurrences remain). Never Karma, never Vitest.89- Select with `byTestId` and put `data-testid` on the elements you assert against.90- Set inputs with `spectator.setInput()`. Never assign to component properties directly.91- Mock stores in `componentProviders`, not `providers`.9293### 5. Three files per component — `.ts` + `.html` + `.scss`9495Inline `template:` and inline `styles:` in the decorator are forbidden. Reference the files with96`templateUrl` and `styleUrls` (plural — matching `docs/frontend/ANGULAR_STANDARDS.md` and the97dominant convention in the workspace).9899### 6. Reuse before creating100101Before creating a new component, in this order:1021031. Check dotCMS's own components — `core-web/libs/ui` (`@dotcms/ui`) and the existing feature libs.1042. Check PrimeNG.1053. Only then create a new component — and say in the PR why neither of the above fit.106107### 7. Every component that renders data handles all four states108109**loading**, **empty**, **error**, **loaded** — explicitly. Never leave a blank render path. If a110template has no branch for a state, that state is a bug.111112### 8. Error handling is mandatory113114No silent failures. No unguarded `.subscribe()`. No empty `catchError`. Handle the error in the service115with `catchError`, surface a user-facing error state in the component, and log the diagnostic detail.116117### 9. Icons are Material Symbols118119```html120<span class="material-symbols-outlined">drag_indicator</span>121```122123The icon name goes in the element's **text content**, not in the class. Fonts are self-hosted via124`core-web/libs/dotcms-scss/shared/_material-symbols-outlined.scss`.125126- Existing PrimeIcons (`pi pi-*`) stay as they are — do **not** mass-migrate them.127- PrimeNG's internal icons are a theming concern and out of scope.128- Do **not** use the deprecated `dot-icon` component.129130### 10. dotCMS-specific naming131132These differ from stock Angular convention, so upstream will not tell you about them:133134| Rule | Do | Don't |135| --- | --- | --- |136| Signals | `$mySignal` (**`$` prefix**) | `mySignal`, `mySignal$` |137| Observables | `myObservable$` (**`$` suffix**) | `$myObservable` |138| Selectors | `dot-` prefix | anything else |139140## Covered in full by the doc — read it, do not rely on this file141142`docs/frontend/ANGULAR_STANDARDS.md` is authoritative for everything below. Upstream Angular guidance143already agrees with us on these, so they are **not** restated here and this file must not grow a second144copy of them:145146- **Subscription teardown** — `inject(DestroyRef)` + `takeUntilDestroyed()`; the legacy `destroy$`147 pattern is not for new work and is not to be mass-migrated.148- **Signal Forms first** — new forms use `@angular/forms/signals`; existing Reactive Forms stay.149- **TypeScript 6** — the transitional `"ignoreDeprecations": "6.0"` flag; new code must not rely on it.150- **Standard modern Angular API** — `input()` / `output()` over decorators, `inject()` over constructor151 injection, `@if` / `@for` (with `track`) / `@switch` over `*ngIf` / `*ngFor` / `*ngSwitch`,152 `[class.x]` / `[style.x]` over `ngClass` / `ngStyle`, and host bindings in the `host: { … }` object153 over `@HostBinding` / `@HostListener`.154155## Before you finish156157- [ ] No `standalone: true` and no `changeDetection` added158- [ ] Any `Eager` component you touched is still `Eager`159- [ ] Three files exist for each new component; no inline template or styles160- [ ] Loading, empty, error, and loaded paths all render something161- [ ] Every stream is torn down with `takeUntilDestroyed(destroyRef)`; every failure path is handled162- [ ] Specs use `@openng/spectator`, `byTestId`, and `setInput()`163- [ ] Verified with `pnpm nx run <project>:test` and `pnpm nx run <project>:lint` — not `ng`