DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
World desk5 min

Optimizing Injection Tokens in Angular: Keeping Unused Library Components Out of Your Bundle

Replace a library's runtime reference to an optional Angular component with a small abstract token and useExisting provider so unused implementation code can be tree-shaken, while keeping InjectionToken identity and provider scope correct.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For Angular library authors, the main way to optimize injection tokens for bundle size is to stop the library from holding a runtime reference to an optional component. Replace that reference with a small abstract class that acts as a token, have the concrete component extend it, and register the component under that token with useExisting. When an application never uses the optional component, its implementation code can become eligible for tree-shaking. Angular’s official guide describes this mechanism; it does not promise a specific size saving, so treat the gain as something to measure in your own build.

Why a token can keep a component in consumer bundles

TypeScript removes type-only references when it compiles to JavaScript. A reference that must exist at runtime, such as passing a component class to a content query or to inject(), survives compilation. Bundlers can only drop code that nothing references, so a library that queries for an optional header component by its class keeps that class, its template, and its styles in the output even when no consumer ever renders the header. The consuming application cannot fix this from the outside, because the reference lives inside the library it imports. The fix belongs in the library’s design. Angular’s guide on optimizing client application size with lightweight injection tokens describes this exact problem.

As an Amazon Associate I earn from qualifying purchases.

The lightweight-token pattern

The pattern splits the library’s API into two parts: a small abstract declaration that the parent uses, and a concrete implementation that the application may never load. Build it in four steps.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Declare an abstract class as the token

    Create an abstract class containing only the members the parent needs. Keep it small, because this declaration is what stays in the bundle.

    export abstract class CardHeader {
      abstract title: string;
    }
  2. Have the optional component extend the token

    The concrete component implements the abstraction by extending it. Its template, styles, and logic are the parts that can be removed when unused.

    @Component({
      selector: 'lib-card-header',
      template: `<h2>{{ title }}</h2>`,
      providers: [{ provide: CardHeader, useExisting: LibCardHeaderComponent }],
    })
    export class LibCardHeaderComponent extends CardHeader {
      title = 'Untitled';
    }
  3. Register the component under the abstract token

    The useExisting provider tells Angular that a request for CardHeader should resolve to the component instance that is already created. The component is therefore reachable through the abstract class, and no separate instance is made.

  4. Have the parent query or inject the abstract token

    The parent refers only to CardHeader, never to the concrete class.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
    @Component({
      selector: 'lib-card',
      template: `<ng-content />`,
    })
    export class LibCardComponent {
      @ContentChild(CardHeader) header?: CardHeader;
    }

    If the application places a lib-card-header inside a card, the query finds it. If it does not, the query returns nothing, and the concrete component never needs to be part of the bundle.

Two conditions decide whether the saving actually happens. The concrete component must not be referenced at runtime anywhere else in the library, for example in an exported array of default components or in a module-level side effect. Any such reference keeps it in the output. Also, the library must not import the concrete component from a file that is itself always loaded.

Use InjectionToken for values without a runtime class

Interfaces, object shapes, and primitive configuration have no runtime representation, so they cannot serve as a token. For these, define an InjectionToken with a type parameter. The generic gives the injected value its type, and the token itself is the runtime identifier.

export interface CardOptions {
  compact: boolean;
}

export const CARD_OPTIONS = new InjectionToken<CardOptions>('CARD_OPTIONS', {
  providedIn: 'root',
  factory: () => ({ compact: false }),
});

Consumers then call private options = inject(CARD_OPTIONS); in a field initializer. A factory-backed token like this can be provided in the root injector, and its factory can call inject() because the factory runs in an injection context. The defining dependency providers guide covers the provider options in more detail.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Token identity is object identity

Angular matches a token by the exact object reference, not by its description string. Two InjectionToken instances with the same description are different tokens. A common failure looks like this: a provider file creates new InjectionToken('CARD_OPTIONS'), and a component file creates a second token with the same text. The component’s injection fails with a NullInjectorError, even though the names match.

The fix is structural. Declare each token once as an exported const in a single file, import that same instance wherever it is provided or injected, and avoid creating tokens inside functions or components. If you re-export tokens through a barrel file, the barrel must re-export the original instance rather than a copy.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choose provider scope separately from the token

The token pattern decides what the library references. Where the provider is registered decides how many instances exist and how long they live. Angular resolves a dependency by walking up the injector hierarchy, from the element injectors of the component tree to the environment injector. The hierarchical injectors guide explains that walk.

Registration Instances and lifetime Typical fit Tree-shaking note
providedIn: 'root' on the service or token One instance shared across the application Globally shared, stateless or app-wide services and configuration Root provisioning can support tree-shaking when the service or token is unused
Component providers array One instance per component, visible to its child tree Isolated state, or overriding a parent’s implementation for a subtree Not stated in the cited guide; the provider exists only where its component is compiled in
Route-level or environment providers One instance per route or environment injector Route-scoped features or per-feature configuration Not stated in the cited guide

Use root provisioning when the service should be shared and tree-shakable. Use a narrower registration when each subtree needs its own instance or a different implementation. A narrower provider does not make a library smaller by itself, so choose scope for correctness first and bundle size second.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Where inject() is allowed

The inject() function works only in an injection context. That includes the constructor of a DI-managed class, field initializers, and factory functions for providers or InjectionTokens. Calling inject() from an arbitrary method, such as ngOnInit, is outside that context and fails. Move the call into a field initializer, which runs during construction, and keep the pattern consistent across the library.

What the official guidance establishes

Angular’s lightweight-token guidance describes a mechanism for client bundle size and tree-shaking. It is not a runtime speed technique, and it does not change how a component renders once it is loaded. The guidance does not include a bundle-size percentage, a benchmark, or a dated measurement. How much you save depends on the size of the optional component, what else references it, and how your library is built and consumed. To check the result, compare production bundles before and after the change with a bundle analyzer, and confirm that the concrete component’s files no longer appear when the component is not used.

Review checklist for library code

  • Does any runtime code reference the concrete optional class directly, through a query, an inject() call, an array, or a side effect?
  • Does the abstract token expose only the members the parent actually uses?
  • Is every InjectionToken declared once as an exported const and imported everywhere it is used?
  • Is root provisioning used only for services that should be shared app-wide?
  • Have you compared production bundles with the optional component unused?

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.