When a new release of your internal Angular UI-kit library makes one app fail to build and another throw injection errors, what do you check and fix?
answer
- how it was built, what was published
- library Angular newer than the app
- two copies of @angular/core
- peerDependencies and a removed export
basics
~20 sCheck that the release is a production, partial build published from dist, that it was not built with a newer Angular than the app, that @angular packages stay peerDependencies so apps get one copy, and whether an export was removed.
solid answer
~40 sWork through the release pipeline. First, was it the default production build, which compiles in partial mode, published from `dist/ui-kit`? A development (full) build is tied to one exact Angular version. Second, compare versions: an app older than the Angular the library was built with is unsupported, and its build can stop with an error asking you to upgrade the app; build the library with the oldest Angular it supports. Third, for the injection errors, look for a second `@angular/core`: if someone moved `@angular/core` or `@angular/common` from `peerDependencies` to `dependencies`, or an app `npm link`s the library without `preserveSymlinks`, the library runs against its own framework copy and valid `inject()` calls can fail with NG0203. Finally, diff `public-api.ts`: a removed or renamed export is a compile error for consumers and needs a major version.
code
json · 12 lines{
"name": "@acme/ui-kit",
"version": "4.2.0",
"peerDependencies": {
"@angular/common": "^22.0.0",
"@angular/core": "^22.0.0"
},
"dependencies": {
"tslib": "^2.3.0"
},
"sideEffects": false
}go deeper
Recall that the framework is a peer dependency of a library and that you publish the production build from dist.
Explain the forward-only version rule for partial output and why two copies of @angular/core break dependency injection.
Lead the audit: classify each symptom, confirm with the install tree and the public API diff, and fix the release pipeline, not just one app.
Set the support policy: which Angular versions the kit targets, how peer ranges and majors move, and how apps are sequenced through upgrades.
## Frame the problem as a release audit Two different failures after one release usually have two different causes, and all of them sit in how the library was **built**, **declared** and **published**. Work through the release in that order rather than debugging the consuming apps. | Symptom | Likely cause | Where to look | |---|---|---| | App build fails, message asks to upgrade the application | library built with a newer Angular than the app | the library workspace's `@angular/*` versions | | App build fails with odd runtime-instruction errors | a full (development) build was published | how the release job invoked `ng build` | | App build fails: "has no exported member" | an export was removed or renamed | `public-api.ts` diff | | Runtime: NG0203 or other injection failures from library code | two copies of `@angular/core` | the library's `package.json`, the app's install tree | ## Step 1: what was built and published 1. **Configuration.** A library generated by `ng generate library` has `defaultConfiguration: "production"`, whose `tsconfig.lib.prod.json` sets `compilationMode: "partial"`. If the release job passed `--configuration development`, it published full-compilation output, which contains private Ivy instructions and is only guaranteed to work with the exact Angular version it was built with. 2. **Folder.** Publish from `dist/ui-kit`, not from `projects/ui-kit`. The Angular documentation's recipe is `ng build my-lib`, then `cd dist/my-lib`, then `npm publish`. ## Step 2: the Angular version the library was built with Partial output is **forward compatible only**: Angular supports an application on the same or a newer version than the one a library was built with, and not an older one. If the UI-kit workspace was upgraded to the next major before the apps, the older apps are now unsupported, and when the library's compiled declarations need the newer compiler, the app build fails with an error saying it depends on a library published with a newer Angular and suggesting an upgrade of the application. Fixes, in order of preference: - rebuild and re-release the library with the **oldest Angular version it supports**; - or keep the new build, bump the library's **major** version, and let apps adopt it when they upgrade Angular; - in both cases, make the `peerDependencies` range say the truth about which Angular versions are supported. ## Step 3: a duplicated framework The library schematic generates a `package.json` with `@angular/core` and `@angular/common` as **peerDependencies**, and the Angular documentation says every `@angular/*` package a library uses should be a peer dependency, so that the library and the app share the exact same framework module. If a release moves them into `dependencies`, a package manager may install a second copy under the library. The library's components and its `inject()` calls then talk to a different `@angular/core` from the one that bootstrapped the app. That copy has its own injection context and its own tokens, so a call that is perfectly placed in a constructor can still fail with **NG0203** ("inject() must be called from an injection context"), and providers registered through one copy are invisible to the other. The same duplication appears in local development when an app consumes the library through `npm link` or `pnpm link`. The Angular documentation's fix for linked libraries is `preserveSymlinks: true` in the app's build options, plus excluding the library from the dev server's `prebundle`, "essential to avoid multiple copies of the dependent node packages". How to confirm: list where `@angular/core` is installed in the failing app (for example `npm ls @angular/core`); more than one version or location is the smoking gun. ## Step 4: the public API diff Compare the new `public-api.ts` (or the generated `.d.ts` in `dist`) against the previous release. Removing or renaming an export breaks every consumer that imported it; that is a semver-major change even if the code "moved" internally. ## Preventing the next one - Run the release from CI with the default production build only. - Pin the library workspace to the oldest supported Angular, and test against the newest. - Keep `@angular/*` and other shared singletons in `peerDependencies`. - Build one consuming app against the packed tarball before publishing.
- Why is `tslib` a regular dependency when `@angular/core` is a peer?The Angular Package Format recommends tslib as a direct dependency because its version is tied to the TypeScript version that compiled the library; the helpers are stateless, so a second copy is harmless. `@angular/core` holds runtime state such as injectors and tokens, so the app and library must share one copy.
- How do you reproduce the linked-library injection error and fix it locally?Link the built `dist/ui-kit` into an app outside the workspace with your package manager's link command and serve the app; without extra config the library can resolve `@angular/core` from its own location. Set `preserveSymlinks: true` in the app's build options and add the library to the dev server's `prebundle.exclude`, as the Angular documentation describes.
- Which Angular version should the UI-kit's own workspace use?The oldest version the library claims to support in `peerDependencies`, because partial output is supported in apps on the same or newer Angular but not older. Test against the newest supported version separately, and move the minimum only in a major release of the library.
saying these in an interview costs you the question
- Listing @angular/core in dependencies guarantees consumers get a compatible version.
- Partial compilation makes a library safe to use in older Angular apps.
- NG0203 from library code always means inject() is misplaced in the library.
- Removing an export is a minor change if the class still exists internally.
- Publishing from projects/ui-kit is fine because the source is the same.