Repository navigation
Version 1.1.0 moves the starter to Angular 22, Nx 23 and TypeScript 6 with strict mode on, and lets every signed-in user manage their own account. It is the largest release since 1.0.0, and it asks something of every fork: read Fork migration below before merging.
The first area is the toolchain. Angular 22.0, Nx 23.1 and TypeScript 6.0 land together, because each one requires the others (#534). Components now rely on Angular 22's default OnPush change detection, and a lint rule keeps it that way (#551). @ngrx/signals moves from a pinned pre-release to the stable 22.0.0 (#553). TypeScript's strict mode is now on across the workspace (#560). Turning it on also exposed a real bug: three API routes documented a generic "Forbidden" error instead of their actual reason for refusing (#555). Node.js moves to the 24.20.0 LTS release, and a new docs/node-support-policy.md explains why the starter follows the LTS line instead of adopting Node 26 (#535, #536). Prettier is pinned to one exact version, so formatting no longer depends on the day you installed (#559).
The second is account self-service. Any signed-in user can open /account and change their own name. A before → after summary is shown before anything is saved, and the new endpoint can only ever edit the caller's own account (#454). Changing the email address stays admin-only until the new address can be verified; that is planned for 1.2.0 (#588). The sidebar footer now shows the signed-in user, with a menu for Account, Settings and Log out (#586), and the desktop sidebar no longer collapses (#604). GET /api/auth/me now reads the account from the database instead of from the session token. A rename therefore appears on the next page load, and a disabled user is signed out on their next navigation instead of up to 15 minutes later (#454).
The third is user administration. Admins edit a user's profile, roles and status in one drawer, check the changes as a before → after list, and save them in a single transaction. A failed role change can no longer leave an edit half-applied (#453). The user and role drawers no longer accept an invalid form (#589). Permission descriptions now follow the selected language (#572), and the row actions menu is built on the new shared Menu component (#601).
The fourth is testing and CI. Until now the integration suite could not fail: a failing run still exited successfully. It now fails properly, so a fork may see failures that were already there (#585). The code generators work again; they had broken under ES modules (#590). The shared Playwright fixture no longer hangs at teardown (#557), and the upstream guard checks run again when a stacked pull request is retargeted (#583). On the release side, an unused nx release configuration is removed and a protection rule for version tags is specified (#486).
Documentation gains a single reference for Angular dependency injection (#482) and setup instructions for the Zed editor (#575).
Fork migration
Every fork:
- #535 - You must use Node.js 24.20.0 or later (
nvm usereads.nvmrc), and move any Node version pins in your own CI or deployment configuration to 24.20. - #534, #535 - After merging, you must reinstall from scratch with
rm -rf node_modules package-lock.json && npm install. A partial install cannot resolve Angular 22's dependencies. If you add a dependency that runs install scripts, approve it inallowScripts(npm install-scripts lslists the skipped ones). - #534 - You must add
"rootDir": "../.."tocompilerOptionsinapps/<your-app>/tsconfig.app.jsonandapps/<your-app>/tsconfig.spec.json, and"rootDir": ".."toapps/<your-app>/e2e/tsconfig.json. These files do not change on merge, and without the setting TypeScript 6 failsnpm run typecheckwith TS6059 ("not under 'rootDir'"), because your app importspackages/*sources from outside its own folder. The production build still passes. - #534 - You must list the public hostnames your app serves, either in
security.allowedHostsunder thebuildtarget'soptionsinapps/<your-app>/project.json(for example"security": { "allowedHosts": ["my-app.example.com"] }) or asnew AngularAppEngine({ allowedHosts: [...] })inapps/<your-app>/src/server.ts. Angular 22 answers400 Bad Request(Header "host" with value "…" is not allowed.) to every SSR request whose host is not listed, where Angular 21 fell back to client-side rendering, so every page returns 400 while/api/*routes and healthchecks keep passing. TheNG_ALLOWED_HOSTSenvironment variable has no effect, because the starter usesAngularAppEngine. If your app runs behind a trusted proxy that sets the forwarded headers and SSR needs the public host or protocol, also passtrustProxyHeaders: ['x-forwarded-host', 'x-forwarded-proto']tonew AngularAppEngine(): Angular 22 ignoresX-Forwarded-*headers by default. - #551 - You must remove every
changeDetection: ChangeDetectionStrategy.OnPush(and theChangeDetectionStrategyimport it leaves unused) from your own.tsfiles, including specs, stories and your app's copies of reference-app components, and from anypageorui-componentgenerator templates you customised. Ano-restricted-syntaxrule ineslint.config.mjsnow failsnpm run linton**/*.tswith "Angular 22 enables OnPush change detection by default — remove the redundant explicitchangeDetection: ChangeDetectionStrategy.OnPush". - #560, #555, #556 - You must fix the errors that strict mode reports in your own app, including your copies of reference-app code.
"strict": trueis now intsconfig.base.json, TypeScript 6 enables strict anyway unless it is set tofalse, and the errors fail bothnpm run typecheckandnpm run build. PortgetAuthenticatedUser(c)intosrc/api/middlewares/verify-access-token.middleware.tsand use it instead of(c as AuthenticatedContext).userin your role, user-management and user-role controllers and inverify-permissions.middleware.ts. In specs (verify-access-token.middleware.spec.ts,verify-permissions.middleware.spec.tsand the permission, role, user-role, user-management and auth controller specs), set and read the user throughsetAuthenticatedUser/readAuthenticatedUserfrom the newverify-access-token.middleware.mock.ts. Declare any401,403or500response after...commonResponses, in your copies ofrole.routes.ts(deleteRoleRoute,assignPermissionsRoute) anduser-management.routes.ts(updateUserStatusRoute) and in your own routes: strict mode reports the earlier order as TS2783, and the spread would overwrite your description. Apply the null-safety fixes inverify-health.ts(responseTimeMs ?? undefined),nav-section.ts(@let sectionName = section().name),app.routes.server.spec.ts(match?.renderMode) andauth.mapper.spec.ts(createdAt: null, updatedAt: null). If your fork is not ready, set"strict": falsein thecompilerOptionsofapps/<your-app>/tsconfig.jsonas a temporary, documented divergence; removing the key does not turn strict off. - #559 - After merging, you must run
npm run formatand commit the result. Prettier is now pinned to exactly 3.9.6, which reformats some arrow functions (your copy ofdomain/user-management/managed-user.mapper.tsamong them), and thechecktarget'sprettier --checkfails until you do. - #585 - You must copy the integration exit-code fix into your own app, because it does not arrive on merge. In
apps/<your-app>/src/api/integration/setup/embedded-pg-test-db.ts, addimport asyncExitHook from 'async-exit-hook'and callasyncExitHook.unhookEvent('beforeExit')andasyncExitHook.unhookEvent('exit')at module level. Inglobal-setup.ts, drop the static import of./embedded-pg-test-dband useawait import('./embedded-pg-test-db')inside the branch that starts the cluster (setup) and the one that stops it (teardown). Until you do, your app'stest-integrationexits 0 even when tests or the global setup fail, and the newtest-integration-setup-guardchecks only the reference app. If you also copyembedded-pg-test-db.spec.ts, narrow the exclude inapps/<your-app>/vitest.config.tsfromsrc/api/integration/**tosrc/api/integration/**/*.integration.spec.tsso the spec runs. Expect failures that already existed to appear in your app's first run after the copy. - #585 - Expect the reference app's
test-integrationto fail on your first run after merging if it was already failing, for example whenINTEGRATION_TEST_ADMIN_PASSWORDis unset: those failures used to exit 0.npm run ciandnpm run ci:verifygain the Batch 1 targettest-integration-setup-guard, which starts embedded Postgres, and the CItest-integrationjob gains a guard step. - #601 - You must import
RowActionfrom@resetshop/ui/row-actions-menu/row-actions-menuin your copies ofusers/users-list/users-list.tsandroles/roles-list/roles-list.ts, and in any code of your own. Therow-action-itemfile and theRowActionItemcomponent are deleted frompackages/ui, so imports from@resetshop/ui/row-actions-menu/row-action-itemfail to compile after merging. UseMenu(@resetshop/ui/menu/menu) where you usedRowActionItem. - #534 - Wherever your code or specs call a
canMatchguard directly (aCanMatchFn, orcanMatch()on a class implementingCanMatch), you must pass the thirdcurrentSnapshotargument. Angular 22 makes it required, and two-argument calls fail the type-check. - #534 - If a component of yours declares both
x = model()and anxChangeoutput, you must replace the model withxInput = input(<initial>, { alias: 'x' })plusx = linkedSignal(this.xInput). Angular 22 rejects the duplicatexChangeoutput at build time. - #534, #551 - Angular 22 runs every component without a
changeDetectionsetting as OnPush. You should check the components in your own code that had none, and make any that relied on the old eager checking work under OnPush (signals, immutable updates). A component that must stay eager keepschangeDetection: ChangeDetectionStrategy.Eageronly with aneslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detectioncomment, because angular-eslint 22 reports any explicitEageror the deprecatedDefaultas a lint error. - #534 - You should remove
withFetch()(and its import) fromprovideHttpClient(...)inapps/<your-app>/src/app/app.config.ts. Angular 22 deprecates it because fetch is now the default backend. Nothing fails while it stays, but a future major will remove it. - #534 - You should review template expressions
a?.bwhose result reaches a=== null/!== nullcomparison, a pipe, a function-call argument or an input that tellsnullfromundefined. In Angular 22,a?.byieldsundefinedinstead ofnullwhenais nullish. - #557 - You should add
await page.unrouteAll({ behavior: 'ignoreErrors' })right afterawait use(page)in the page fixture ofapps/<your-app>/e2e/fixtures/index.ts. It removes the/apiroute handler before Playwright closes the context, which otherwise can hang Firefox end-to-end teardown past the test timeout under parallel load.
If your fork ships its own language files (the TranslationSchema type-check lists every missing or unknown key):
- #454, #453, #482, #572 - You must add the top-level
ACCOUNT.*keys,USERS.DETAIL.EDIT.*(includingCONFIRM_DIALOG.*),DASHBOARD.USERS.NAVandPERMISSIONS.DESCRIPTIONS.PERMISSIONS.DESCRIPTIONSis a map keyed by permission identifier (module:resource:action); the type-check needs only the property ({}passes) and does not check its entries. When you copy the translated permission views, add one entry per identifier in yourPERMISSION_DEFINITIONS, and copy their coverage specproviders/i18n/permission-descriptions.spec.ts, which checks them. - #453, #586 - You must remove
USERS.DETAIL.PROFILE.{SAVE,SUCCESS_TOAST},USERS.DETAIL.ROLES.{EDIT_BUTTON,DRAWER_TITLE,ROLES_LABEL,SUCCESS_TOAST}andDASHBOARD.HOME.DESCRIPTIONS.SETTINGS, renameDASHBOARD.SECTIONS.SETTINGStoDASHBOARD.SECTIONS.MAINTENANCE, and then update your code that still uses them. The type-check catches thetranslation.instant(...)calls inedit-user-roles-drawer.tsanduser-profile-section.tsand theRecord<string, TranslationKey>map indashboard-home.ts. It does not catch the| translatepipes inedit-user-roles-drawer.ts,user-profile-section.tsanduser-roles-section.ts, or the sectionnameindashboard.navigation.ts, which render the raw key instead: search your templates and navigation config for each removed key. - #453, #454, #572, #586 - You should remove the entries for the removed keys from your
translation.mock.ts. It is aRecord<string, string>, so the type-check does not flag it. When you copy specs from the reference app, also copy the entries they assert on (ACCOUNT.*,USERS.DETAIL.EDIT.*,HEALTH.NAV,DASHBOARD.HOME.DESCRIPTIONS.HEALTH) and theTranslationStubinterface, or those specs see raw keys.
If your fork changed or reuses any of the following:
- #534 - Your
tsconfig.base.json, if it diverges from upstream: you must keep"ignoreDeprecations": "6.0","types": ["*"]and"noUncheckedSideEffectImports": falsein itscompilerOptions. WithoutignoreDeprecations, TypeScript 6 fails every project on the deprecatedbaseUrl(TS5101). - #534 - Your own components that implement
FormValueControlwith a two-waytouched = model<boolean>(): you must change it to atouched = input<boolean>()and atouch = output<void>(), aspackages/uiSelectandComboboxnow do. - #534 - Your own generators or Nx plugins: if they import
@nx/devkit/src/*,@nx/js/src/*,@nx/eslint/src/*,@nx/web/src/*or@nx/angular/src/utils, you must switch to the package root or its/internalentry, and renamecreateNodesV2/CreateNodesV2tocreateNodes/CreateNodes. Nx 23 removed those subpaths. - #590 - Your own generators: you must follow the ES module rules and add them to the
GENERATORSlist inscripts/check-generators-load-as-esm.mjs. Locate templates withresolveTemplateDir(import.meta.url): lint now rejects__dirnameand__filenameunderpackages/generators/. - #453 - The Edit Roles drawer or the inline profile form, if you adopt the combined Edit User drawer: you should move your changes into
edit-user-drawer.ts. If you copy the newUserManagementService, callers ofupdateUser/updateUserStatusmust read.userfrom theUserUpdateResultit returns. - #453 - Your copy of the
PATCH /api/users/{id}handler (user-management.controller.ts): you should add[USER_ROLE_ERRORS.ROLES_NOT_FOUND, 400]and[USER_ROLE_ERRORS.NON_REMOVABLE_ROLES, 400]toERROR_STATUS_MAPand widen the status type ofresolveErrorStatusto include400. Unknown or non-removable role IDs otherwise surface as an unmapped500. - #453 - Your component stylesheets: you should replace
var(--color-*)references with the unprefixed theme variables (--foreground,--background,--border, …). Component CSS is not processed with the Tailwind theme, and the build declares no--color-*custom properties for it, neither the@theme inlinetokens intailwind.config.cssnor Tailwind's default palette such as--color-black, so such a declaration silently resolves to nothing. This is why the dialog and drawer backdrops were transparent; thepackages/uistylesheets are fixed on merge. - #454 - Your copy of
GET /api/auth/me(auth.controller.ts): it still answers from the access-token claims, so a renamed user sees their old name until the token refreshes, and a disabled or deleted user stays signed in until the token expires. To sign them out on their next navigation, as the reference app now does, you should addAuthService.getSessionUser(userId): Promise<UserData>(inauth.service.tsand theAuthServiceinterface ininterfaces.ts), which throwsAuthErrorUSER_NOT_FOUNDfor a missing orDELETEDuser andACCOUNT_DISABLEDfor a non-ACTIVEone. In the handler, call it alongside the roles and must-change reads inPromise.all, answer the same401 { error: 'Unauthorized' }for anyAuthError, and rethrow anything else. - #586, #604 - The sidebar, if you port the new one (the user menu from
@resetshop/ui/user-menu/user-menu, with no collapse): you must remove your uses ofsetSidebarCollapsed(),isSidebarCollapsed(),[collapsed]bindings and the Ctrl+B / Cmd+B shortcut. In end-to-end tests, open the menu withDashboardPage.userMenuTriggeranduserMenuItem(name), and dropcollapseButton/expandButton. - #482 - Your copy of
dashboard.routes.ts: you should make pages share one store instance per section. Nestusersandusers/:idas children of ausersparent route (title: 'DASHBOARD.USERS.NAV') that holdsproviders: [provideUsers(), provideRoles(), UsersStore, RolesStore, provideToast()], move theauthorizationchildren's providers onto theauthorizationparent, and remove the providers each page registered for itself. - #589 - Your copies of
create-role-drawer.ts,edit-role-drawer.tsandcreate-user-drawer.ts, and any form of your own that checksform().errors().length === 0: you should useform().valid()instead.errors()lists only the form's own errors, not its fields', so the submit button stays enabled on invalid input.
Behaviour that changes with no action needed:
- #534 - Angular 22 changes defaults that also apply to your app:
provideClientHydration()enables incremental hydration,HttpClientuses the fetch backend, and child routes inherit all parent route params (paramsInheritanceStrategy: 'always'). Opt out withwithNoIncrementalHydration(),withXhr()orwithRouterConfig({ paramsInheritanceStrategy: 'emptyOnly' })only if your app depends on the previous behaviour. - #453 - The reference app's
PATCH /api/users/{id}answers400instead of500for unknown or non-removable role ids. Your app keeps the old behaviour unless you copy the fix (see above). - #486 -
docs/release-process.md§6 specifies a protection ruleset forv*tags. Rulesets are not inherited by forks or mirrors, so create it in your repository if you want protected tags. The unusedreleaseblock is removed fromnx.json; if you runnx release, keep your own.
Full changes
See every merged pull request in v1.1.0.
Changes
Toolchain and runtime
- #534 - Upgrades the starter to Nx 23.1, Angular 22 and TypeScript 6.
- #560 - Enables TypeScript strict mode across the workspace.
- #551 - Relies on Angular 22's default OnPush change detection and enforces it with a lint rule.
- #553 - Moves
@ngrx/signalsfrom the22.0.0-beta.0pin to the stable22.0.0. - #535 - Refreshes Node.js to the 24.20.0 LTS and adds an install-script allowlist.
- #536 - Documents the Node.js support policy for forks.
- #559 - Pins Prettier to one exact version and absorbs the one-time reformat.
- #568 - Fixes the
@swc/helpersversion conflict sonpm lsreports no errors.
Account and navigation
- #454 - Adds the self-service Account page and makes
/meread the account from the database. - #586 - Replaces the sidebar Logout button with a user menu for Account, Settings and Log out.
- #604 - Removes the collapsible desktop sidebar.
User and role administration
- #453 - Edits a user in one drawer, confirmed as a before → after list and saved in one transaction.
- #589 - Stops the user and role drawers from submitting an invalid form.
- #572 - Shows permission descriptions in the selected language.
- #601 - Builds the row actions menu on the shared
Menucomponent.
API and authorization
- #556 - Adds a single accessor for reading the signed-in user in request handlers.
- #555 - Keeps each route's own 403 description in the generated OpenAPI document.
Testing, CI and release
- #585 - Makes the integration suite fail when its run fails.
- #590 - Makes every code generator run as an ES module.
- #557 - Stops the shared Playwright fixture from hanging at teardown.
- #583 - Re-runs the upstream guard checks when a stacked pull request is retargeted.
- #486 - Specifies the version-tag protection rule and removes the unused
nx releaseconfiguration.
Documentation
- #482 - Gathers the Angular dependency-injection guidance into one reference.
- #575 - Documents delivering environment variables from the Zed editor.
- #558 - Fixes the Cookie Security table in the authentication guide.
What's Changed
Merged PRs
- #555 - Fix the OpenAPI 403 response-key ordering that clobbers domain-specific descriptions by @rolivencia in #561
- #556 - Add a getAuthenticatedUser accessor and centralize the AuthenticatedContext cast by @rolivencia in #562
- #557 - Drain the Playwright /api route handler before context teardown by @rolivencia in #564
- #558 - Fix the malformed Cookie Security table in AUTHENTICATION.md by @rolivencia in #563
- #559 - Pin Prettier and absorb the reformatting churn by @rolivencia in #565
- #560 - Enable TypeScript strict mode across the workspace by @rolivencia in #566
- #534 - Upgrade the starter to Nx 23.1 and Angular 22 by @rolivencia in #567
- #551 - Adopt Angular 22 OnPush-by-default and enforce it via ESLint by @rolivencia in #554
- #553 - Replace the @ngrx/signals pre-release pin (22.0.0-beta.0) with a stable 22.0.x by @rolivencia in #569
- #568 - Pre-existing invalid @swc/helpers dedupe surfaced by lockfile regeneration by @rolivencia in #570
- #486 - Protect v* tags and resolve the unwired nx.json release block by @rolivencia in #571
- #572 - Permission catalogue descriptions are not translated (missing i18n) by @rolivencia in #573
- #482 - Consolidate Angular DI guidance into a new .claude/references/angular-di.md by @rolivencia in #574
- #575 - Document Zed editor setup for delivering environment variables by @rolivencia in #577
- #453 - Add ConfirmChangesDialog for before/after confirmation by @rolivencia in #578
- #583 - Re-run the upstream guards when a pull request is retargeted by @rolivencia in #584
- #453 - Return the pre-update snapshot from user updates and map role errors to 400 by @rolivencia in #579
- #453 - Apply profile, role and status changes of PATCH /api/users/{id} in one transaction by @rolivencia in #580
- #453 - Add the user edit diff and its confirmation presenter by @rolivencia in #581
- #453 - Edit users through one drawer with a before/after confirmation by @rolivencia in #582
- #535 - Refresh the Node.js runtime to the latest 24.x LTS by @rolivencia in #587
- #454 - Read the /me identity from the database by @rolivencia in #591
- #454 - Reject disabled and deleted accounts from /me by @rolivencia in #592
- #454 - Add the self-service PATCH /api/users/me endpoint by @rolivencia in #593
- #454 - Add AuthApi.updateProfile and AuthStore.updateProfile by @rolivencia in #594
- #454 - Add the account profile diff, presenter and translation keys by @rolivencia in #595
- #454 - Scope single-use module constants to the function that reads them by @rolivencia in #600
- #454 - Add the read-only /account page by @rolivencia in #596
- #454 - Make the Account page editable behind the confirm-changes dialog by @rolivencia in #597
- #454 - Add Account page e2e coverage by @rolivencia in #598
- #586 - Add Menu, UserMenu and Avatar components for the sidebar user tile by @rolivencia in #602
- #585 - test-integration exits 0 when the integration global setup fails, masking a red gate by @rolivencia in #606
- #586 - Show the signed-in user's tile in the sidebar footer by @rolivencia in #603
- #586 - Remove the Account and Settings sidebar links and dashboard cards by @rolivencia in #605
- #601 - Build RowActionsMenu on the standalone Menu component by @rolivencia in #607
- #604 - Remove the collapsible desktop sidebar: always expanded on desktop, drawer at narrow widths by @rolivencia in #608
- #590 - Generators fail with "__dirname is not defined" because @resetshop/generators is an ES module by @rolivencia in #609
- #536 - Document the Node.js support policy for forks by @rolivencia in #610
- #589 - Admin user and role drawers treat invalid forms as valid because they read only root-level errors by @rolivencia in #611
- [#612] - Release v1.1.0 by @rolivencia in #613
- Release v1.1.0 by @github-actions[bot] in #614
Full Changelog: v1.0.2...v1.1.0