Shared Angular component library for BC Ministry of Health applications. Provides
reusable standalone components, services, models, and helpers consumed by apps such
as fpcare.
Packaged with ng-packagr into Angular Package Format output, with a secondary entry
point at moh-common-lib-angular/captcha. The workspace root stays
private: true; the thing that gets published is the built package in
dist/moh-common-lib-angular. Published to npm as
moh-common-lib-angular. See
Packaging and release below. Component docs:
https://bcgov.github.io/moh-common-lib-angular/
| Layer | Choice | Version |
|---|---|---|
| Runtime | Node.js | 22 LTS (pinned in .nvmrc, declared in engines) |
| Package manager | npm | 10.9+ (lockfile v3) |
| Framework | Angular | 19.2 (standalone components) |
| Language | TypeScript | 5.7, strict: true, target ES2022 |
| Build | @angular-devkit/build-angular:application |
19.2 |
| Tests | Jest via @angular-builders/jest |
Jest 30, jsdom, no browser |
| Lint | ESLint flat config + angular-eslint | ESLint 9 |
| Format | Prettier, enforced on commit by Husky 9 + lint-staged | Prettier 3.7 |
| Styles | Sass (.scss), Bootstrap 5 tokens |
see Styling below |
| Component docs | Storybook, @storybook/angular webpack framework |
10.6 |
Run nvm use in the repo root to pick up the pinned Node version.
- A library.
src/lib/andsrc/helpers/, packaged by ng-packagr through the library project inprojects/common-lib/. Consumers get compiled Angular Package Format output, not raw TypeScript. - A showcase app.
src/app/is a runnable Angular application (ng serve) that renders library components for manual inspection.
src/public-api.ts is a hand-maintained barrel file, and it is the single source of
truth for what is public. A component that is not exported there is not part of the
library surface, even if the file exists.
The library project directory holds only configuration. The sources stay in src/, and
projects/common-lib/ng-package.json reaches back to them with a relative entryFile.
projects/
common-lib/ Library packaging config only, no source.
package.json The PUBLISHED manifest: name, version, peerDependencies.
ng-package.json ng-packagr config. entryFile points at ../../src/public-api.ts.
tsconfig.lib.json Library compile. Partial Ivy mode. Excludes src/app and specs.
src/
public-api.ts Export barrel. The public surface. Edit this to publish anything.
main.ts, index.html Showcase app entry point.
styles.scss Global stylesheet placeholder (see Styling caveat below).
app/ Showcase app only. Not shipped as library surface.
helpers/ deburr, scroll, error type. Plus test-helpers.ts, which is
test-only and does import @angular/core/testing.
lib/
components/ One directory per component.
models/ Data models and the abstract form base classes.
services/ HTTP, logging, navigation guards, page state.
styles/ _tokens.scss (design tokens in use), variables.scss and
common-mixins.scss (both currently unused, see Styling)
Most library components follow the same shape. Match it when adding a new one.
- Standalone. No component is declared in an NgModule. Each declares its own
importsarray.SharedCoreModuleimports and re-exports every standalone component and validator directive for consumers whose own components are still NgModule-declared; see Compatibility module below. - Selector prefix
common-. For examplecommon-phn,common-address. - Form controls extend
AbstractFormControl(src/lib/models/abstract-form-control.ts). That base is@Directive()-decorated and supplies two inherited inputs,disabledanderrorMessage, plus an abstractlabelthat each subclass must declare itself with a component-specific default. It also implements theControlValueAccessorplumbing and theregisterValidation()helper that lets a control attach its own validator to itsNgControl. - Composite components extend
Base(src/lib/models/base.ts), which supplies a uuidobjectId.AbstractFormControlextendsBasetoo, so both paths get it. Individual form controls use it to build a uniquelabelforId, for example'city_' + this.objectId. - Errors render through
ErrorContainerComponent(common-error-container). This is the shared inline-error primitive. It is also the elementscrollToError()looks for, so a control that renders its own error markup instead will silently break scroll-to-first-error for the whole page. - Single-value form controls expose
valueas a getter/setter pair with a matchingvalueChangeoutput, so[(value)]banana-in-a-box binding works. Ten of the nineteen exported components do this. Composite and layout components do not, andCheckboxComponentusesdata/dataChangefor historical reasons rather than because it is a pattern worth copying.
src/lib/components/<name>/with<name>.component.ts,.html, and.scssif it needs styles.- Extend
AbstractFormControlfor a form control, orBasefor anything composite. TakedisabledanderrorMessagefrom the base rather than redeclaring them.labelis abstract, so you must declare it yourself with a default that suits the component, for example@Input() label = 'City';. Omitting it will not compile. - Selector
common-<name>. Declare every dependency in the component's ownimportsarray. AvoidNO_ERRORS_SCHEMA; it hides real template errors, and the places it is used in this repo are legacy, not a pattern to copy. - Render validation errors through
common-error-container. - Add
<name>.component.spec.ts. Every component insrc/lib/components/has one.src/helpers/test-helpers.tshas the shared fixture builders (createTestingModule,setInput,getDebugInlineError). Assert against rendered DOM, not against a property you just set on the fixture, and drive form controls through a real event rather than by calling the handler. - Export it from
src/public-api.ts. Skipping this is the usual reason a consuming app cannot see a new component. - Add it to the catalogue table below and, if useful, to the showcase in
src/app/app.component.html. - Add
<name>.component.stories.tsbeside the component for Storybook.
Address and location:
| Component | Selector | Purpose |
|---|---|---|
AddressComponent |
common-address |
Composite address block: street lines, city, province, country, postal code |
AddressValidatorComponent |
common-address-validator |
Debounced address typeahead against an external lookup service (currently non-functional) |
CityComponent |
common-city |
City text input with character validation |
CountryComponent |
common-country |
Country picker, ng-select dropdown or free text |
ProvinceComponent |
common-province |
Province picker, ng-select dropdown or free text (currently non-functional: throws on select) |
StreetComponent |
common-street |
Street input, optional BC Geocoder typeahead (currently non-functional) |
Identity:
| Component | Selector | Purpose |
|---|---|---|
NameComponent |
common-name |
Single name field |
FullNameComponent |
common-full-name |
First, middle, last name bound to a Person |
PhnComponent |
common-phn |
Masked BC Personal Health Number with checksum validation (currently non-functional: value changes are not reported) |
SinComponent |
common-sin |
Masked Social Insurance Number |
Contact:
| Component | Selector | Purpose |
|---|---|---|
EmailComponent |
common-email |
Email input with pattern validation |
PhoneNumberComponent |
common-phone-number |
Masked North American phone number |
Generic inputs:
| Component | Selector | Purpose |
|---|---|---|
CheckboxComponent |
common-checkbox |
Single checkbox with label |
RadioComponent |
common-radio |
Radio group driven by an IRadioItems[] list |
ButtonComponent |
common-button |
Bootstrap-styled button, re-emits click as btnClick |
XiconButtonComponent |
common-xicon-button |
Small "x" close/remove button, emits clickEvent |
DropdownComponent |
common-dropdown |
Generic ng-select single-select dropdown; object items display item.label, value is the whole item |
DateComponent |
common-date |
Date input with optional range or future/past restriction |
| PostalCodeComponent | common-postal-code | Masked Canadian postal code, optional BC-only check |
Layout and presentation:
| Component | Selector | Purpose |
|---|---|---|
ErrorContainerComponent |
common-error-container |
Inline error primitive used by every form control |
HeaderComponent |
common-header |
BC Gov header with logo and skip-to-content link |
AccordionCommonComponent |
common-accordion |
Collapsible section wrapper (currently non-functional) |
PageFrameworkComponent |
common-page-framework |
Page layout with main column and optional aside |
PageSectionComponent |
common-page-section |
Section layout within a page |
CoreBreadcrumbComponent |
common-core-breadcrumb |
Breadcrumb bar with left, center and right slots |
FormActionBarComponent |
common-form-action-bar |
Sticky submit bar. Place it after common-page-framework, not inside |
WizardProgressBarComponent |
common-wizard-progress-bar |
Multi-page progress bar, active step derived from the route |
ConfirmTemplateComponent |
common-confirm-template |
Confirmation panel, icon driven by ApiStatusCodes |
SampleModalComponent |
common-sample-modal |
Modal displaying labelled sample images |
ConsentModalComponent |
common-consent-modal |
Information collection notice gated on an agree checkbox |
Upload:
| Component | Selector | Purpose |
|---|---|---|
FileUploaderComponent |
common-file-uploader |
Drag-and-drop image and PDF upload. PDFs rasterise one image per page |
ThumbnailComponent |
common-thumbnail |
Uploaded image with click-to-enlarge and remove |
Not exported means not shipped. ng-packagr compiles only the import graph reachable
from src/public-api.ts, so anything the barrel does not reach is absent from the
published package entirely, not merely hidden behind a deep import path. These exist in
this repo and are usable if you build against the sources, but a consumer installing the
package cannot import them by any path.
| Component | Why |
|---|---|
PasswordComponent (common-password) |
Pulls in zxcvbn, which not every consuming app carries. To ship it, export it from public-api.ts and add zxcvbn as an optional peer dependency, the same way pdfjs-dist is handled for FileUploaderComponent. |
All seven are exported, each paired with a bare ValidatorFn for reactive forms.
The selectors are attribute selectors, which matters under standalone: an
unmatched attribute raises no compile error, so a consumer who forgets to add the
directive class to their imports gets a control that silently validates nothing.
Import the class, not just the attribute.
| Directive | Selector | Validator function | Error key |
|---|---|---|---|
ValidateCityDirective |
[commonValidateCity] |
commonValidateCity |
invalidChar |
ValidateStreetDirective |
[commonValidateStreet] |
commonValidateStreet |
invalidChar |
ValidateRegionDirective |
[commonValidateRegion] |
commonValidateRegion |
invalidChar |
ValidatePostalcodeDirective |
[commonValidatePostalcode] |
commonValidatePostalcode(hasMask, bcOnly) |
pattern, invalidChar, invalidBCPostal |
ValidateBcPostalDirective |
[commonValidateBcPostal] |
commonValidateBcPostal |
invalidBCPostal (deprecated, use commonValidatePostalcode) |
ValidateNameDirective |
[commonValidateName] |
commonValidateName |
invalidChar (deprecated) |
DuplicateCheckDirective |
[commonDuplicateCheck] |
commonDuplicateCheck(dupList) |
duplicate |
moh-common-lib-angular/captcha is packaged separately, so an app that never renders
a captcha does not pay for it. Configured by projects/common-lib/captcha/ng-package.json
with sources in src/captcha/.
| Export | Kind | Notes |
|---|---|---|
CaptchaComponent |
component | common-captcha. Image or audio challenge, emits a JWT through onValidToken |
CaptchaModule |
NgModule | Compatibility module for apps that import an NgModule. Imports and exports the standalone component and provides CaptchaDataService |
CaptchaDataService |
service | Fetch, verify and audio calls against the captcha API |
CAPTCHA_STATE |
enum | The six states the component moves through |
import { CaptchaModule } from 'moh-common-lib-angular/captcha';| Service | Purpose |
|---|---|
AbstractHttpService |
Base class for HTTP API services. Supplies get/post, uuid generation, attachment upload. |
GeocoderService |
BC Geocoder address lookup, returns GeoAddressResult[] |
CommonLogger / CommonLogEvents |
Splunk-bound application logging. log()/logError() are void; a subclass that needs the underlying Subscription/Observable overrides them and calls the protected _log()/_logError() instead (since 2.5.0) |
PageStateService |
Tracks per-page completion for multi-page form flows |
ContainerService |
Observable bus between a page container and its action bar |
AbstractPageGuardService |
Contract an app implements to control wizard navigation |
DefaultPageGuardService |
Default implementation, reads BYPASS_GUARDS and START_PAGE_URL tokens |
LoadPageGuardService |
CanActivate guard enforcing sequential navigation |
PdfService |
Thin wrapper over pdfjs-dist, used internally by FileUploaderComponent for PDF uploads |
RouteGuardService |
Older guard, superseded by LoadPageGuardService (deprecated) |
AbstractPgCheckService |
Older guard contract (deprecated) |
CheckCompleteBaseService |
Older page-completion tracking (deprecated) |
New work should use AbstractPageGuardService plus LoadPageGuardService and
PageStateService. The three deprecated services are kept only for existing consumers.
| Export | Kind | Notes |
|---|---|---|
Base |
class | uuid objectId. Most components extend this. |
Address, Person |
class | Data models with copy() and validation helpers |
AbstractForm, AbstractBaseForm, AbstractReactForm |
class | Page-level form base classes |
AbstractFormControl |
class | Base for every form control component |
Container, WizardProgressItem |
class, type | Wizard container state |
CommonImage and friends |
class, enum | Image model with sizing and error codes |
SimpleDate, ErrorMessage |
interface | |
ApiStatusCodes |
enum | Selects the icon shown by ConfirmTemplateComponent |
GeoAddressResult |
interface | Result shape returned by GeocoderService |
FileUploaderMsg |
interface | Customizes the required-file error message on FileUploaderComponent |
LETTER, NUMBER, SPACE |
const | Input mask character constants |
deburr |
function | Strips diacritics |
scrollTo, scrollToError |
function | Scroll utilities. scrollToError targets common-error-container |
MoHCommonLibraryError |
class | Library error type |
- Node.js 22 LTS. Run
nvm useto pick up.nvmrc. - yalc for local publishing:
npm install -g yalc
The Angular CLI is a devDependency, so npx ng works without a global install. If you
prefer a global CLI, pin it: npm install -g @angular/cli@19.
npm ci # clean install from the lockfile
npm start # ng serve, runs the showcase app
npm run build # ng build, showcase app, output to dist/showcase/
npm run build:lib # ng-packagr, library output to dist/moh-common-lib-angular/
npm run pack:lib # build:lib, then npm pack, producing the publishable tarball
npm test # ng test, Jest via @angular-builders/jest
npm run lint # ng lint, ESLint flat config
npm run prettier # format src/
npm run storybook # ng run moh-common-lib-angular:storybook, dev server
npm run build-storybook # ng run moh-common-lib-angular:build-storybook, output to dist/storybook/.husky/pre-commit runs npx lint-staged, which acts on staged files only:
src/**/*.{ts,html}getsprettier --writetheneslintsrc/**/*.{js,scss}getsprettier --write
Prettier rewrites are re-staged automatically, so formatting fixes land in the same commit. ESLint runs after, and an error aborts the commit while a warning does not. See the lint entry under Known gaps for why the three highest-volume rules are warnings.
To see what the hook would do without committing:
npx lint-staged --diffBypassing it (git commit --no-verify) should be rare and deliberate.
Jest runs in Node with jsdom. No browser or Chrome binary is needed.
npx jest src/lib/components/phn/phn.component.spec.ts # one spec
npx jest --watch # watch mode
npx jest --coverage # coverage to coverage/Coverage is collected from src/lib/**/*.ts and src/captcha/**/*.ts, excluding
specs, modules, models, interfaces, and constants. src/helpers/ and src/app/ are
excluded entirely. Overall coverage is around 73% of statements; the branch figure is
lower, and src/lib/components/file-uploader/ is the weakest because its canvas and
FileReader paths cannot execute under jsdom.
Current state: 60 suites, 547 tests, all passing.
Stories live beside the component they document, as <name>.component.stories.ts,
both under src/lib/components/ and for the captcha secondary entry point. Config
is in .storybook/.
npm run storybook # dev server
npm run build-storybook # static build to dist/storybook/Preview CSS (Bootstrap 5, Font Awesome) is wired through the storybook and
build-storybook targets' styles array in angular.json only. The library itself
still ships no CSS and expects the consuming app to supply Bootstrap and Font Awesome.
The showcase app's own build target has no styles array, so unlike Storybook it
currently loads neither. The a11y addon panel runs automated accessibility checks
against each story. Compodoc is disabled. PR validation builds Storybook; see
Automated validation and releases below.
npm run build:lib # ng-packagr, to dist/moh-common-lib-angular/
npm run pack:lib # the above, then npm pack, giving a .tgz to hand to a consumerprojects/common-lib/package.json carries the published
name, version, and peerDependencies. Set release versions with npm run release:set-version -- X.Y.Z, not by editing only the library manifest. The existing script updates and
checks all four locations: package.json, package-lock.json
at both its top level and packages[""].version, and the library manifest. It does not
commit or tag. Publish the built tarball, never the workspace root (private: true).
The library compiles in partial Ivy mode (compilationMode in tsconfig.lib.json).
Full mode makes ng-packagr write a prepublishOnly guard that aborts any publish, so do
not remove that setting. npm run build:lib also copies LICENSE and the npm package's
own README, projects/common-lib/README.md, into dist,
which ng-packagr cannot do itself because both sit outside its project root. This root
README stays the GitHub/contributor doc and is never copied into the package. Update the
Catalogue in both files when exports change.
The showcase app writes to dist/showcase rather than dist/ so that the two builds do
not delete each other's output.
- PR validation targets PRs into
main, including forks, and runsnpm ci, non-watching CI tests, lint,npm run build-storybook, andnpm run pack:lib. It has onlycontents: read, no publishing secrets, and nopull_request_targettrigger. - Release runs on pushes to
maininbcgov/moh-common-lib-angular. It compares the library version at the push'sbeforecommit with the accepted push commit. An unchanged version skips all release work; merging these workflows while both versions remain2.0.0does not publish. A missing/zero baseline fails closed. There is no tag trigger. - The same workflow also accepts a manual
workflow_dispatchrun, for recovery when a push-triggered run failed before npm publication. From Actions > Release > Run workflow, selectmainand enter the version already committed inprojects/common-lib/package.json; a mismatched input or a non-mainref fails the run instead of releasing. That in-workflowmaincheck is only a backstop: because a dispatched run executes the workflow file from whichever branch is selected, the control that actually prevents a release from another branch is thenpmenvironment's deployment-branch rule, restricted tomainin setup step 2 below. The dispatch route shares every later gate with the push route: strictX.Y.Zand four-location version checks, preflight, tests, lint, pack, and thenpmenvironment's existing-tag/release/npm checks. - Dispatch limits: it cannot recover a run that already published to npm but failed at
the tag or GitHub Release step, because the existing npm version fails closed; create
the tag and release by hand instead. It tags the current
mainhead, so any commits merged after the version bump are included in that tag. - A release must have a strict stable
X.Y.Zversion (no leading zeroes, prerelease, or build suffix), matching versions in all four locations, a new tag, and a completed exact## X.Y.Z (YYYY-MM-DD)section in CHANGELOG.md. Avprefix in the heading is also accepted. Notes are that section, not generated commit summaries. The existingrelease:preflightruns before dependency installation against a clean checkout with full history and tags. Its Git fetch uses a step-scoped, read-only token header in process memory; checkout never persists credentials. - The read-only preparation job repeats install, tests, lint, and pack at the immutable
accepted commit, then uploads the tarball and notes. The separate
npmenvironment job verifies their SHA-256 checksums and the tarball's identity, checks remote state again, and publishes that tarball without rebuilding it. Only this job hasid-token: writeandcontents: write. It checks out no code, installs no project dependencies, and suppresses npm lifecycle scripts. No build job receives OIDC. - npm publication uses the public registry, OIDC provenance, public access, and the
stable
latestdist-tag. Only after npm succeeds does the job create a newvX.Y.ZGit ref at the tested commit and a matching GitHub Release with the reviewed notes. It never moves or deletes an existing tag, includingv2.0.0. - Both workflows use the latest Node 22 patch (must remain >=22.14.0) and explicitly install npm 11.5.1 for trusted publishing. Repository engine ranges and dependency versions are unchanged. Official actions are pinned to verified immutable SHAs; no dependency/artifact build caches are restored. Lint errors block, while the existing warnings remain warnings.
These are follow-through instructions, not authorization to commit, push, tag, publish,
or change GitHub/npm settings. The npm account is istevens_npm. Trusted publishing
must be configured on an existing npm package, so the first publication was a separate,
authenticated human operation; step 4 records it.
-
In GitHub Settings, protect
mainwith a branch rule/ruleset: require PRs, reviewed approvals (including version and changelog), dismiss stale approvals, require theInstall, test, lint, packcheck fromPR validation, and require branches to be up to date. Block direct pushes, force pushes, deletion, and administrative bypass. This workflow trusts pushes tomain; YAML alone cannot enforce PR-only merges. Do not enable merge queues without adding and validatingmerge_groupsupport. Require trusted review for workflow changes. Protect release tags against update and deletion while permitting the repository Actions token to create new tags. -
Enable GitHub Actions and allow the four SHA-pinned official actions used here:
actions/checkout,actions/setup-node,actions/upload-artifact, andactions/download-artifact. Organization policy must permit the release job's explicitcontents: writeandid-token: write; the default can remain read-only. Create a GitHub environment namednpm, restrict deployment branches tomain, add required release reviewers, and prevent self-review/bypass where available. The workflow always names this environment; protection settings require human setup. -
Merge the workflow PR through review when separately approved. With unchanged
2.0.0, the release job skips. Do not create, move, or deletev2.0.0to trigger it. -
Done.
moh-common-lib-angular@2.0.0was published manually byistevens_npmon 2026-09-14, built from tagv2.0.0(cb06ab47043d8f63d57a5a23b3953066df951fa8) on Node 22. Registrydist.shasumis82bbc0f0d82ce56099468c072421233cb445622e. This bootstrap did not use Actions OIDC, so 2.0.0 carries no npm provenance. Tagv2.0.0and its GitHub Release already existed and were left unchanged. -
Sign in to npmjs.com as
istevens_npm. Open packagemoh-common-lib-angular, Settings, Trusted publishing, Add trusted publisher, GitHub Actions. Enter these case-sensitive values:Setting Value Organization or user bcgov(not the npm account)Repository moh-common-lib-angularWorkflow filename release.yml(filename only)Environment name npmAllowed actions Explicitly allow direct npm publish, not stage-onlyNo
NPM_TOKENorNODE_AUTH_TOKENsecret is needed. After verifying trusted publishing, select npm Publishing access: Require two-factor authentication and disallow tokens. Keep human 2FA recovery available. npm does not validate these trust settings when saved; only a future approved release can verify OIDC end to end.
Choose the next stable version based on consumer impact, then use the existing tooling on the release PR branch. For example, only if review determines a patch is appropriate:
npm run release:set-version -- 2.0.1
npm run release:notes -- 2.0.1 v2.0.0The notes command prints a draft; insert it into CHANGELOG.md, replace
every TODO, remove generator comments, and review the summary and consumer actions.
Review all four version locations in the PR. Commit/push only with separate approval.
Do not pre-create the tag or manually publish: the accepted main push performs the
release after validation and environment approval. Ordinary PRs leave the version alone.
Release concurrency never cancels an active run, including a publish awaiting approval.
GitHub concurrency retains one pending run, replaces older pending runs, and does not
guarantee ordering. An ordinary unchanged-version push can replace a pending release
push. Merge no additional PRs while a release is queued/running; confirm each release
completes before merging the next. A displaced release is not automatically replayed:
use its original Actions run's Re-run all jobs if available, after checking remote
state and coordinating all releases. Otherwise use the manual workflow_dispatch run
described above, or stop for an approved recovery plan; an empty push will not trigger
publication of an unchanged version.
Reruns fail closed if the target npm version, tag, or GitHub Release already exists,
even if it looks correct. They also block registry/network ambiguity and prevent an
older version from replacing a newer stable latest. There is no automatic republish,
tag repair, rollback, or "already exists, therefore success" shortcut.
- Before npm publication: fix the cause, confirm no version/tag/release exists, and rerun. Failed-job reruns reuse the uploaded artifact; all-job reruns validate and pack the same event commit again. Expired/missing artifacts block publishing (retention is 30 days). Do not substitute a locally rebuilt artifact in a failed-job rerun.
- If npm may have succeeded: stop and inspect the original run's artifact, checksums,
accepted commit, registry
dist.integrity, and npm provenance. Compute the retained tarball's SHA-512 SRI, for example withnode -e 'const fs=require("node:fs"), crypto=require("node:crypto"); console.log("sha512-"+crypto.createHash("sha512").update(fs.readFileSync(process.argv[1])).digest("base64"))' /absolute/path/to/package.tgz, and compare it withnpm view moh-common-lib-angular@X.Y.Z dist.integrity --registry=https://registry.npmjs.org. Verify any existing tag resolves to the original tested commit and compare any Release notes to the retained notes. Any mismatch or missing evidence blocks recovery. - Only after those checks and explicit approval may a maintainer create a missing tag at that exact commit and/or a missing GitHub Release from the exact reviewed notes. Never republish the version or move/delete an existing tag. If evidence cannot be verified, hand off for a new reviewed version rather than guessing or unpublishing.
Serve the built package, not the sources.
npm run build:lib
cd dist/moh-common-lib-angular && yalc publish
yalc add moh-common-lib-angular # in the consuming app
npm run build:lib # after each library change
cd dist/moh-common-lib-angular && yalc pushPrefer yalc over npm link. npm link symlinks, which gives Angular two copies of
@angular/core and produces NG0203 injection errors. yalc copies.
Design tokens live in src/lib/styles/_tokens.scss and are consumed with
@use '../../styles/tokens' as vars;. That file holds only tokens with a call site;
extract another from variables.scss when a component needs it.
src/lib/styles/variables.scss is the older token file and cannot be loaded:
Bootstrap 5's _variables.scss calls _assert-ascending, which is namespaced rather
than global under @use, so any component importing it fails with Undefined mixin.
Nothing imports it, which is why this went unnoticed. common-mixins.scss is valid but
unused. Neither is wired into the build, and angular.json has no styles array, so
src/styles.scss is not part of it either. Component templates use Bootstrap 5 utility classes and Font
Awesome icon classes that this repo does not bundle. The consuming application supplies
them. A new component should assume the same and rely on Bootstrap classes rather than
importing variables.scss, until the styling pipeline is reconnected.
Recorded so they are not rediscovered as surprises.
-
Four runtime dependencies are declared in the root
devDependencies:ngx-mask,date-fns,uuid, andzxcvbn.uuidbacksBase, which nearly every component extends. For the first three this is cosmetic for consumers, because they are declared aspeerDependenciesinprojects/common-lib/package.jsonand npm installs them; the root manifest simply misfiles them.zxcvbnis different: its only importer isPasswordComponent, which the barrel does not export, so it is not in the shipped bundle and is not a peer dependency. Adding it to the peer set would make every consumer install a package no shipped code uses. ExportingPasswordComponentlater means addingzxcvbnback as a peer in the same change. The peer set is verifiable: everyfrom '<package>'indist/moh-common-lib-angular/fesm2022/*.mjsshould have a matching peer entry, and nothing else should.pdfjs-distis declared as an optional peer dependency, used only byFileUploaderComponentfor PDF uploads.PdfServiceloads it through a dynamicimport()with a.catch()attached. The.catch()is what keeps it optional: a bundler resolves a bare dynamic-import specifier at build time, so without it an app that never uploads a PDF still fails to build. Do not remove it. -
npm run lintreports 538 problems: 0 errors and 538 warnings. ESLint had never actually run in this repo before the Node 22 work added the missingeslintdependency, so the bulk of these are long-standing findings rather than regressions. None is auto-fixable. The count grew from 411 at the 2.0.0 release as ported components and their specs landed; the rule mix did not change.Rule Count @typescript-eslint/no-explicit-any189 @typescript-eslint/member-ordering172 no-underscore-dangle119 @typescript-eslint/no-unused-vars48 @angular-eslint/no-output-native10 These rules are set to
warnrather thanerroron purpose, so that the pre-commit hook can block genuinely new problems without rejecting every commit that touches an already-affected file. The first three are high-volume style debt,no-underscore-danglebeing theControlValueAccessoridiom.no-output-nativeflags publicblurandselectoutputs on eight components; renaming those breaks every consuming app's template bindings, so it needs an API decision rather than a drive-by fix. Promote each back toerrorineslint.config.jsas it is cleared.The 2 blocking
templateAccessibilityerrors recorded here previously are gone. They covered a click handler inpassword.component.htmlwith no keyboard equivalent, fixed by replacing thespanwith a realbuttonin the validation-hardening work. -
npm audit: 8 high-severity advisories in production dependencies (@angular/*,@ng-select/ng-select), 33 total including dev. Clearing the Angular ones requires an Angular 20 upgrade. -
AccordionCommonComponentstill references removed ngx-bootstrap markup. It emits<accordion>, an unknown element, and compiles only because of itsNO_ERRORS_SCHEMA, so the panel never collapses and[isOpen]is inert. Either implement the toggle in plain markup, the waythumbnail,sample-modalandconsent-modalwere, or withdraw it from the barrel.SampleModalComponentwas on this list for the same reason and is fixed: it now drives a plain-markup dialog from anisOpenflag. -
Four components are broken on
mainand are not fixed here.common-phnis missing both its(ngModelChange)and(blur)bindings, so it never reports a value and never shows an error.common-provincethrows on dropdown select, becauseng-selectemits a value where the handler readsevent.target.value.common-streetrenders a label with its input commented out.common-address-validatornever calls the geocoder, its typeahead bindings having been commented out with the ngx-bootstrap removal. All four are rendered by consuming apps. -
[errorMessage]is documented on every form control and tested on none.CountryComponentalso handles it differently from its siblings: it prepends the label in its template instead of substituting{label}, so an override written the documented way renders a literal{label}on that component only. -
The Jest transform is driven by a preset that does not declare support for the installed Jest.
@angular-builders/jest@19.0.1pinsjest-preset-angular@14.5.4exactly, and that copy peer-requiresjest ^29while the repo runs Jest 30. Anoverridesentry forces the resolution. All 547 tests pass, but nothing guards this combination; it clears on an@angular-builders/jestmajor that supports Jest 30. Note this is separate from the rootjest-preset-angular@16.2.0thatsetup-jest.tsimports. The two-version split predates the Node 22 work. -
2.0.0 has no npm provenance. It was published manually to bootstrap the package. Releases from the workflow carry provenance once trusted publishing is configured (step 5 of the human setup above).
SharedCoreModule (from src/public-api.ts, since 2.5.0) is an NgModule that
imports and re-exports every standalone component and validator directive this entry
point exports: every component in the Catalogue above and all seven validator
directives. PasswordComponent is not included, since it is not exported from the
entry point either. Captcha is not included; import CaptchaModule from the separate
moh-common-lib-angular/captcha entry point for that.
Import SharedCoreModule into an NgModule whose own components are still
NgModule-declared (not standalone) and need common-* tags available in their
templates - it saves importing each standalone component individually. A standalone
component's own imports array should import the individual components/directives it
uses directly instead; pulling in the whole module there adds nothing it needs.