LEARN · DEBUGGING GUIDE

Debugging Angular NullInjectorError: No Provider for Injectable

Angular's NullInjectorError with 'no provider' means the injector hierarchy can't find a registered provider for the requested token. This guide covers the real-world causes and fixes.

IntermediateAngular8 min read

What this usually means

Angular's dependency injection relies on providers registered at different levels (module, component, or root). The NullInjectorError 'no provider' tells you that the injector hierarchy—starting from the component's injector up to the root injector—does not contain a provider for the requested token. This usually happens because a service is not listed in the module's providers array, is not marked as providedIn: 'root', or is scoped to a lazy-loaded module that creates a child injector. Other causes include tree-shaking removing the provider during production builds, or using a token that doesn't match any provider (e.g., injection token vs class mismatch).

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Check the exact token name in the error message and search your codebase for its provider registration (e.g., @Injectable({providedIn: 'root'}) or module providers array).
  • 2Look at the stack trace: the first component or module in the trace is where injection fails—check that its parent module or the component itself has the provider.
  • 3If the error occurs in a lazy-loaded module, verify the service is provided using providedIn: 'root' or imported via a shared module that provides it.
  • 4Run `ng build --optimization=false` to rule out tree-shaking removing the provider in production.
  • 5Add a temporary provider at the component level (providers: [MyService]) and see if the error disappears—this confirms the root cause is missing provider.
  • 6Check for circular dependencies: they can prevent providers from being registered correctly; look for 'Circular dependency detected' warnings in the console.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchError stack trace: identifies the failing component and injection point
  • searchModule files: check providers arrays of NgModule imports and exports
  • searchService class decorator: @Injectable({providedIn: 'root'}) vs no providedIn
  • searchLazy-loaded module definitions: Angular creates a child injector that only sees providers from the lazy module and its imports
  • searchShared module: ensure it exports the service provider if the service is provided in a shared module
  • searchProduction build output: examine main.js and chunk files to confirm the service class is present
  • searchangular.json optimization settings: tree-shaking can remove unused providers
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningService is not provided in any module and missing providedIn: 'root' or providedIn: 'any'
  • warningLazy-loaded module creates a child injector that does not include providers from parent module unless explicitly imported
  • warningService is provided in a module that is not imported by the module where injection happens
  • warningProduction build tree-shaking removes the provider because Angular thinks it's unused (common with barrel exports or indirect usage)
  • warningCircular dependency causing Angular to fail registering the provider
  • warningUsing an abstract class or injection token without a proper provider (e.g., useClass, useExisting)
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildAdd @Injectable({providedIn: 'root'}) to the service to make it a singleton available application-wide
  • buildIf the service must be scoped to a module, add it to the module's providers array and ensure the module is imported where needed
  • buildFor lazy-loaded modules, provide the service in a shared module that is imported by both the root module and the lazy module
  • buildIf tree-shaking is the issue, ensure the service is referenced in a way that prevents removal (e.g., used in a component constructor, or add a side-effect import)
  • buildUse a factory provider or useClass to resolve token mismatches
  • buildRestructure code to break circular dependencies by extracting shared interfaces or using forwardRef
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedReload the application and navigate to the route that previously threw the error—should proceed without console error
  • verifiedRun `ng test` and verify that unit tests for the affected component pass (ensure TestBed config includes the provider)
  • verifiedInspect the browser's DevTools Angular Injector tree (Augury or Angular DevTools) to confirm the provider exists at the correct injector level
  • verifiedBuild with --source-map and verify the service class is in the chunk (bundle size check)
  • verifiedCheck that the service instance is the same across components (for singletons) by comparing object references
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningAdding the same service to multiple providers arrays, creating multiple instances inadvertently
  • warningUsing providedIn: 'root' for services that should be scoped to a lazy module (creates unnecessary singletons)
  • warningForgetting to import the module that provides the service in the module where injection occurs
  • warningTreating the error as a browser cache issue—it's almost always a code configuration problem
  • warningBlindly adding providers to the root module for lazy-loaded services without considering the injector hierarchy
  • warningIgnoring circular dependency warnings—they often precede provider issues
( 07 )War story

NullInjectorError after lazy-loading a feature module

Senior Frontend EngineerAngular 14, Nx monorepo, lazy-loaded routes, custom AuthService

Timeline

  1. 09:15Deploy new feature module 'AdminPanel' with lazy loading via loadChildren
  2. 09:20User reports blank page when navigating to /admin
  3. 09:22I reproduce: console shows NullInjectorError: No provider for AuthService
  4. 09:25Check AuthService: @Injectable({providedIn: 'root'}) is present
  5. 09:30Search codebase: AuthService is provided in CoreModule, which is imported in AppModule
  6. 09:35Realize AdminModule is lazy-loaded and does not import CoreModule
  7. 09:40AuthService uses HttpClient and relies on HTTP_INTERCEPTORS from CoreModule
  8. 09:45Fix: Remove providedIn: 'root' from AuthService, add it to CoreModule's providers, and ensure CoreModule is imported in AppModule only
  9. 09:50Deploy fix, verify /admin loads correctly

We rolled out a new admin panel as a lazy-loaded module. Shortly after, users started seeing blank screens when accessing /admin. The console error was clear: NullInjectorError: No provider for AuthService. At first glance, AuthService was decorated with providedIn: 'root', so I assumed it would be available everywhere. But the stack trace pointed to the AdminComponent constructor.

I checked the CoreModule where AuthService was originally provided. It was imported in AppModule, which should make it available to the entire app. However, I forgot that lazy-loaded modules create their own injector children. While providedIn: 'root' normally creates a singleton at the root injector, the error persisted because AuthService also depended on HttpClient and interceptors configured only in CoreModule. The lazy-loaded module's injector couldn't find those interceptors because CoreModule wasn't imported in the lazy module.

The fix was to remove providedIn: 'root' from AuthService and explicitly provide it in CoreModule's providers. Then I made sure CoreModule was imported only in AppModule (to avoid duplicate instances) and that the lazy-loaded module imported any modules it needed. After the fix, the admin panel loaded fine. The lesson: always verify that lazy-loaded modules have access to all required providers, and don't rely solely on providedIn: 'root' when dependencies have module-level configurations.

Root cause

Lazy-loaded module's child injector could not resolve AuthService due to missing provider of its dependencies (HTTP_INTERCEPTORS) that were configured in a parent module not imported by the lazy module.

The fix

Removed providedIn: 'root' from AuthService, added it to CoreModule's providers, and ensured CoreModule was imported only in AppModule. The lazy module did not need to import CoreModule because its dependencies were resolved via the root injector after the fix.

The lesson

When lazy-loading modules, be aware of the injector hierarchy: child injectors cannot see providers from parent modules unless those modules are imported. Use providedIn: 'root' only for truly standalone services with no module-specific dependencies.

( 08 )Understanding Angular's Injector Hierarchy

Angular creates a tree of injectors mirroring the module and component tree. The root injector is at the top, followed by module injectors (one per eagerly loaded module), and component injectors. A lazy-loaded module gets its own child injector that inherits from the root injector but not from other eagerly loaded modules. This means if a service is provided in an eagerly loaded module (e.g., CoreModule), it is not automatically available in a lazy-loaded module unless the service is provided at the root (providedIn: 'root') or the lazy module imports the module that provides it.

The NullInjectorError 'no provider' occurs when the injector walks up the hierarchy to the root and finds no provider for the token. Common mistake: thinking that importing a module in AppModule makes its providers available to all lazy modules. It does not—only the root injector and the lazy module's own providers are visible. To fix, either mark the service as providedIn: 'root', or re-provide it in the lazy module (or through a shared module imported by both).

( 09 )Tree-Shaking and Production Builds

Angular's production build uses tree-shaking to remove unused code. If a service is not directly referenced in any component constructor or provider array, it may be eliminated. This is common when using barrel exports (index.ts) or indirect references. The result: the service class is absent from the bundle, causing NullInjectorError in production even though it works in development.

To diagnose, compare the bundle with and without optimization. Run `ng build --optimization=false` to see if the error disappears. If so, ensure the service is referenced somewhere that prevents tree-shaking: either in a constructor, in a provider array, or by using a side-effect import (e.g., `import './service'`). Alternatively, use the `@Injectable({providedIn: 'root'})` decorator which tells Angular to always include the service in the bundle.

( 10 )Circular Dependencies and Provider Registration

Angular's DI can fail silently or produce NullInjectorError when there are circular dependencies between modules or services. For example, if ServiceA depends on ServiceB and ServiceB depends on ServiceA, Angular may fail to register one of them. This often manifests as a 'Circular dependency detected' warning in the console, followed by NullInjectorError.

To resolve, break the cycle by using interfaces, forwardRef, or restructuring. Use forwardRef when you have a circular reference in the same file: `@Inject(forwardRef(() => MyService))`. Better yet, extract shared functionality into a third service or use an injection token for the dependency.

( 11 )Multiple Instances vs. Singleton Confusion

A common pitfall is accidentally creating multiple instances of a service by providing it in multiple modules or components. For example, if a service is provided in a lazy-loaded module and also in a component, each lazy load creates a new instance. This can lead to unexpected behavior but not necessarily NullInjectorError. However, if a component expects a singleton but gets a different instance, it may cause state inconsistencies.

To ensure a singleton, use providedIn: 'root'. For module-scoped singletons, provide the service only in that module and never in child modules or components. Use Angular's injection token to verify you're getting the same instance by comparing references in the debugger.

Frequently asked questions

Why does NullInjectorError appear only in lazy-loaded modules but not in eagerly loaded ones?

Lazy-loaded modules create their own child injector that does not inherit providers from other eagerly loaded modules (except the root injector). If a service is provided in an eagerly loaded module (like CoreModule) but not marked as providedIn: 'root', the lazy module's injector cannot find it. To fix, either mark the service as root-provided or import the providing module in the lazy module.

Can Angular's tree-shaking cause NullInjectorError in production?

Yes. If a service is not directly referenced in any component or provider, it can be removed by tree-shaking. This is common when using barrel exports or when the service is only used via dynamic injection. Check by building with --optimization=false; if the error goes away, ensure the service is referenced properly or use providedIn: 'root'.

How do I fix NullInjectorError when using an abstract class as an injection token?

Angular cannot inject an abstract class directly because there is no provider. You need to provide a concrete implementation using useClass, useExisting, or useFactory. For example: { provide: AbstractService, useClass: ConcreteService }. Make sure the provider is registered in the appropriate module or component.

What is the difference between providedIn: 'root' and providedIn: 'any'?

providedIn: 'root' creates a single instance in the root injector, shared across the entire app. providedIn: 'any' creates an instance per module injector (including lazy-loaded modules), so each lazy module gets its own instance. Use 'root' for true singletons, 'any' when you want separate instances per module (e.g., for state scoped to a module).

Why does my unit test throw NullInjectorError even though the service has providedIn: 'root'?

In unit tests, Angular's test bed does not automatically include root providers unless you configure the TestBed. Even with providedIn: 'root', you must add the service to the TestBed providers array or import the module that provides it. Use TestBed.configureTestingModule({ providers: [MyService] }) to resolve.