skip to content

In an Angular CLI library, what is `public-api.ts` for, and how does `ng-package.json` connect it to what consuming apps can import?

level: middleimportance: should knowfreq 42%

answer

  1. the package's front door
  2. lib.entryFile in ng-package.json
  3. one flat file per entry point
  4. not exported means not importable

basics

~10 s

public-api.ts is the library's entry file: ng-package.json names it in lib.entryFile, and ng-packagr packages what it exports into the published entry point. Anything not exported there cannot be imported by consumers by package name.

solid answer

~40 s

`public-api.ts` defines the library's public surface. The generated `ng-package.json` sets `"lib": { "entryFile": "src/public-api.ts" }` and a `dest` such as `../../dist/ui-kit`; the `@angular/build:ng-packagr` builder reads that file from the project root unless its `project` option points elsewhere. ng-packagr compiles everything reachable from the entry file and flattens it into one ES module per entry point plus bundled type declarations, so the import path `ui-kit` exposes exactly what `public-api.ts` exports. A component that a consuming template uses, a `provideUiKit()` function, a service class used as an injection token or a type in a public signature must all be exported there. Internal helpers stay unexported, which lets you refactor them freely without a breaking release.

code

ts · 7 lines
ts
/*
 * Public API Surface of ui-kit
 */
export * from './lib/button/button';
export * from './lib/dialog/dialog';
export { provideUiKit } from './lib/provide-ui-kit';
export type { ButtonVariant } from './lib/button/button-variant';

go deeper

for a junior

Recall that public-api.ts lists what consumers can import, and that forgetting an export makes the import fail.

for a middle

Explain lib.entryFile and dest in ng-package.json, how the builder finds that file, and what flattening into one module per entry point means for imports.

for a senior

Show API discipline: exporting deliberately, spotting a breaking export removal in review, and choosing a secondary entry point for optional code.

for a principal

Weigh how wide a shared library's public surface should be against the cost of keeping every export stable across releases.

## The entry file is the contract Every Angular library built by the Angular CLI has one **entry file** per entry point. For the primary entry point, the library schematic generates `src/public-api.ts` and names it in `ng-package.json`: ```json { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/ui-kit", "lib": { "entryFile": "src/public-api.ts" } } ``` - **`lib.entryFile`** tells ng-packagr where the public API starts. - **`dest`** is where the packaged output is written; the workspace `tsconfig` path mapping points at the same folder. - The `@angular/build:ng-packagr` builder looks for `ng-package.json` in the project's root; its `project` option can point at another file, and its `tsConfig` option (set per configuration) picks the TypeScript config. The file name itself is configurable: `ng g lib ui-kit --entry-file index` generates `src/index.ts` and writes that into `entryFile` instead. ## What "public" means after packaging ng-packagr follows every import reachable from the entry file, compiles it, and emits one **flattened ES module (FESM)** file per entry point plus bundled type declarations, in the **Angular Package Format** that `@angular/core` itself uses. So in the published package: 1. `import { Button } from 'ui-kit'` works only if `public-api.ts` exports `Button`, directly or through an `export *` of a file that does. 2. A class used internally but never exported is still compiled into the bundle if something public uses it, but consumers have no import path to it. 3. Paths into the source tree, such as `ui-kit/src/lib/button`, do not exist in the output; the package's `exports` map describes only its entry points. ## What typically has to be exported | Artifact | Why consumers need it | |---|---| | Standalone components, directives, pipes | to list them in a component's `imports` | | An NgModule (for `--standalone false` libraries) | to import it into an NgModule-based app | | `provideUiKit()` or similar provider functions | to register library-wide providers in `bootstrapApplication` | | Services and `InjectionToken`s consumers inject | the class or token is the lookup key for `inject()` | | Types and interfaces used in public inputs or functions | so consumers can type their own code against them | The Angular documentation recommends that services declare their own providers (`providedIn`) and that a library registering global providers expose a `provideXyz()` function, rather than asking apps to list classes by hand. ## Standalone and NgModule libraries The schematic's `--standalone` option (default `true`) decides what the starter `public-api.ts` exports: - **Standalone (default):** it exports the starter component file only. Consumers add the component straight to a component's `imports` array, or to an NgModule's `imports`, since standalone components can be imported there too. - **`--standalone false`:** the schematic also generates an NgModule next to the component and `public-api.ts` exports both files. NgModule-based consumers import the module; its `exports` list decides which declarations their templates can use. Either way, the rule is the same: a declaration that consumers use in a template must be reachable from the entry file, and for NgModule libraries it must also be listed in the module's `exports`. Two gates, two places to forget. ## Keeping the surface deliberate - **Export on purpose.** A blanket `export *` from every folder publishes internals you will then have to keep stable. - **Treat removals as breaking.** Dropping or renaming an export from `public-api.ts` breaks consumers at compile time; that is a major version. - **Review the surface on each release.** The generated `.d.ts` in `dist/<name>` shows exactly what consumers see; diffing it between releases catches accidental removals and accidental additions alike. - **Keep implementation types private.** If a public function returns an internal class, consumers can call its public methods even though they cannot import it; prefer returning an exported interface. - **Use secondary entry points for optional areas**, such as testing helpers, instead of stuffing everything into the primary file. ## A quick way to catch mistakes Because the workspace maps the library name to `dist`, an app in the same workspace consumes the packaged output. If you add a component to the library and forget to export it, the app's import from `'ui-kit'` fails right away with a "has no exported member" style TypeScript error — the same failure an external consumer would see. That is one reason the mapping should not point at the library's source.

  • A consumer asks to import `ui-kit/src/lib/internal-utils`; why not allow it?
    Deep paths into the source tree do not exist in the packaged output, which exposes only the entry points in its `exports` map. If the helper is genuinely useful, export it from `public-api.ts` or from a secondary entry point, and accept that it is now public API you must keep stable.
  • How do you rename the entry file when generating the library?
    Pass `--entry-file`, for example `ng g lib ui-kit --entry-file index`. The schematic creates `src/index.ts` and writes `src/index.ts` into `lib.entryFile` in `ng-package.json`. Renaming it later means editing both the file and that setting.

saying these in an interview costs you the question

  • Everything in the library's src folder is importable once published.
  • Removing an export from public-api.ts is a harmless internal refactor.
  • Services with providedIn root never need to be exported.
  • The entry file is set in angular.json, not in ng-package.json.
  • Consumers can deep-import any file under the published package.