Skip to content

Fix: Angular NullInjectorError: No provider for X

FixDevs · (Updated: )

Part of:  React & Frontend Errors

Quick Answer

How to fix Angular NullInjectorError No provider for service caused by missing providers, wrong module imports, standalone components, and lazy-loaded module issues.

The Error

Your Angular application crashes with:

NullInjectorError: R3InjectorError(AppModule)[MyService -> MyService]:
  NullInjectorError: No provider for MyService!

Or variations:

NullInjectorError: No provider for HttpClient!
NullInjectorError: No provider for Router!
NullInjectorError: R3InjectorError(Standalone[MyComponent])[MyService -> MyService]:
  NullInjectorError: No provider for MyService!
StaticInjectorError(AppModule)[MyComponent -> MyService]:
  NullInjectorError: No provider for MyService!

Angular’s dependency injection system cannot find a provider for the requested service. The service is not registered anywhere that is visible to the component requesting it.

Why This Happens

Angular uses dependency injection (DI) to provide services to components. When a component or service requests a dependency through its constructor, Angular looks for a provider that knows how to create that dependency.

The lookup is hierarchical. Angular starts at the injector closest to the component and walks up through parent injectors until it either finds a matching provider or reaches the root. Standalone components add their own injector. Lazy-loaded routes get their own injector. A service registered at the wrong level is invisible to consumers at other levels. The chain printed in the error message — R3InjectorError(AppModule)[MyService -> HelperService] — is the actual injector path Angular searched. Read it left to right to see exactly where the lookup started and which dependency failed.

The second source of confusion is the R3 vs Static prefix in the error. R3InjectorError comes from the Ivy renderer, which has been the default since Angular 9. StaticInjectorError comes from the older View Engine renderer, which was removed in Angular 17. If you see a StaticInjectorError from a recent project, you are looking at a copy-pasted Stack Overflow answer from 2019 — the fix patterns still apply but the exact wording in newer Angular tooling is R3InjectorError.

If no provider is found in the injector hierarchy, Angular throws NullInjectorError.

Common causes:

  • Missing @Injectable() decorator or missing providedIn property.
  • Service not added to providers array in a module or component.
  • Missing module import. HttpClient needs HttpClientModule, Router needs RouterModule.
  • Standalone component does not import the required providers.
  • Lazy-loaded module has its own injector and cannot see root-level services from other lazy modules.
  • Wrong import path. Importing from the wrong file or barrel export.

Platform and Environment Differences

The same NullInjectorError surfaces differently depending on your build mode, OS, and rendering target.

AOT vs JIT compilation. Angular CLI uses Ahead-of-Time (AOT) compilation by default for both ng serve and ng build since Angular 9. AOT validates the injector graph at build time, so many provider mistakes fail the compile rather than throwing at runtime. Just-in-Time (JIT) compilation (legacy, only available via custom builders) defers that validation to the browser, so the same project can compile cleanly under JIT and crash on the first navigation with NullInjectorError. If a service works locally but breaks after deployment, confirm you are not silently switching modes between environments.

Case sensitivity in module imports across OSes. macOS and Windows use case-insensitive filesystems by default. Linux is case-sensitive. An import like from './services/MyService' resolves on macOS even if the file is myservice.ts, but fails in CI on Linux. The failure mode in Angular is subtle: TypeScript reports Cannot find module, but if your build uses a fallback resolver, you may end up with two compiled copies of the same class — one referenced by the provider, one injected by the consumer — and DI raises NullInjectorError because the two MyService references are not the same identity. Set "forceConsistentCasingInFileNames": true in tsconfig.json so the build fails fast on the developer machine.

ng serve vs ng build vs Angular Universal (SSR). ng serve runs an in-memory dev server with source maps and hot reload. ng build produces production bundles with tree shaking. Angular Universal (now @angular/ssr) runs the same component tree on Node.js during SSR. Each surface has its own bootstrap entry point — main.ts, main.server.ts, server.ts — and each must register the same providers. Forgetting provideHttpClient() in the server bootstrap is a very common cause of NullInjectorError: No provider for HttpClient that only appears in SSR logs and never on the client.

Ivy (default since Angular 9) vs View Engine (removed in Angular 17). Ivy generates per-component injector code and supports tree-shakable providers. View Engine relied on the central module graph. Migration guides from the Ivy transition still circulate online, but most of the View Engine workarounds (entryComponents, ANALYZE_FOR_ENTRY_COMPONENTS) are dead and produce NullInjectorError if pasted into a modern project.

Standalone bootstrap vs AppModule bootstrap. Angular 14 introduced standalone components, Angular 17 made them the default. Standalone apps use bootstrapApplication(AppComponent, { providers: [...] }) and have no AppModule. Tutorials written before that change show platformBrowserDynamic().bootstrapModule(AppModule), which still works but registers providers in a different place. Mixing the two — declaring providers in AppModule while bootstrapping with bootstrapApplication — silently drops the providers.

Fix 1: Use providedIn: ‘root’

The simplest approach. Make the service available application-wide:

Broken — missing @Injectable or providedIn:

// Service without proper decorator
export class MyService {
  getData() { return []; }
}

Fixed:

@Injectable({
  providedIn: 'root'  // Available everywhere, tree-shakable
})
export class MyService {
  getData() { return []; }
}

providedIn: 'root' registers the service with the root injector. It is the recommended approach for most services because:

  • The service is available everywhere without manual registration.
  • It is tree-shakable — if no component injects it, it is removed from the bundle.
  • It creates a singleton (one instance for the entire app).

Pro Tip: Use providedIn: 'root' for almost all services. Only use module-level providers when you need multiple instances, lazy-loaded scoping, or testing overrides.

Fix 2: Add the Service to Module Providers

If you cannot use providedIn: 'root', register in a module:

@NgModule({
  providers: [MyService],  // Register the service here
  declarations: [MyComponent],
})
export class MyModule {}

For component-level providers (new instance per component):

@Component({
  selector: 'app-my-component',
  template: '...',
  providers: [MyService],  // New instance for each component
})
export class MyComponent {
  constructor(private myService: MyService) {}
}

For lazy-loaded modules:

@NgModule({
  imports: [CommonModule],
  providers: [LazyService],  // Only available within this lazy module
})
export class LazyModule {}

Fix 3: Fix HttpClient Provider

The most common NullInjectorErrorHttpClient needs its module:

Broken:

@Component({...})
export class MyComponent {
  constructor(private http: HttpClient) {}  // NullInjectorError!
}

Fixed — NgModule-based app:

// app.module.ts
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,  // Add this!
  ],
})
export class AppModule {}

Fixed — standalone component (Angular 15+):

// app.config.ts (Angular 17+)
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
  ],
};

Fixed — standalone component importing directly:

@Component({
  standalone: true,
  imports: [HttpClientModule],  // Or use provideHttpClient()
  template: '...',
})
export class MyComponent {
  constructor(private http: HttpClient) {}
}

Common Mistake: Importing HttpClientModule in a lazy-loaded module instead of the root AppModule. HttpClientModule should be imported once at the root level. Importing it in multiple modules creates multiple HttpClient instances, which can cause interceptor issues.

Fix 4: Fix Standalone Component Providers

Angular 14+ standalone components have their own provider scope:

Broken — standalone component missing providers:

@Component({
  standalone: true,
  template: '<p>{{ data }}</p>',
})
export class MyComponent {
  constructor(private myService: MyService) {}  // NullInjectorError if MyService not provided
}

Fixed — service uses providedIn: 'root':

@Injectable({ providedIn: 'root' })
export class MyService { ... }
// No changes needed in the component

Fixed — provide at the component level:

@Component({
  standalone: true,
  providers: [MyService],
  template: '<p>{{ data }}</p>',
})
export class MyComponent {
  constructor(private myService: MyService) {}
}

Fixed — provide at the application level:

// main.ts
bootstrapApplication(AppComponent, {
  providers: [
    MyService,
    provideRouter(routes),
    provideHttpClient(),
  ],
});

For deeper standalone wiring problems, see Fix: Angular standalone component error.

Fix 5: Fix Router and Forms Providers

Common built-in services that need their modules or providers:

Router:

// NgModule
@NgModule({
  imports: [RouterModule.forRoot(routes)],
})
export class AppModule {}

// Standalone
bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)],
});

Forms:

// NgModule — import FormsModule or ReactiveFormsModule
@NgModule({
  imports: [FormsModule, ReactiveFormsModule],
})
export class AppModule {}

// Standalone component
@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
})
export class MyFormComponent {}

Animations:

// NgModule
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [BrowserAnimationsModule],
})
export class AppModule {}

// Standalone
import { provideAnimations } from '@angular/platform-browser/animations';

bootstrapApplication(AppComponent, {
  providers: [provideAnimations()],
});

Fix 6: Fix Injection Token Issues

For non-class dependencies, use InjectionToken:

import { InjectionToken } from '@angular/core';

export const API_URL = new InjectionToken<string>('API_URL');

// Provide the token
@NgModule({
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' },
  ],
})
export class AppModule {}

// Inject the token
@Injectable({ providedIn: 'root' })
export class ApiService {
  constructor(@Inject(API_URL) private apiUrl: string) {}
}

For optional dependencies:

@Injectable({ providedIn: 'root' })
export class MyService {
  constructor(@Optional() private logger?: LoggerService) {
    // logger is null if no provider exists — no error!
  }
}

Fix 7: Fix Testing Providers

NullInjectorError in tests usually means the TestBed is missing providers:

describe('MyComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [MyComponent],
      providers: [
        MyService,
        // Or use a mock:
        { provide: MyService, useValue: { getData: () => [] } },
      ],
      imports: [
        HttpClientTestingModule,  // Use testing module, not HttpClientModule
      ],
    }).compileComponents();
  });
});

For standalone components in tests:

await TestBed.configureTestingModule({
  imports: [MyStandaloneComponent],
  providers: [
    { provide: MyService, useValue: mockService },
    provideHttpClient(),
  ],
}).compileComponents();

Fix 8: Debug the Injector Hierarchy

When you cannot figure out where to provide a service:

Check the error message carefully. The injector chain shows the path:

NullInjectorError: R3InjectorError(LazyModule)[MyService -> HelperService]:
  NullInjectorError: No provider for HelperService!

This means MyService depends on HelperService, and HelperService has no provider.

Trace the dependency chain:

@Injectable({ providedIn: 'root' })
export class MyService {
  constructor(private helper: HelperService) {}  // HelperService must also be provided!
}

// Fix: Make HelperService also providedIn: 'root'
@Injectable({ providedIn: 'root' })
export class HelperService { ... }

Check barrel exports:

// If you import from an index.ts barrel file:
import { MyService } from './services';
// Make sure the barrel actually re-exports it:
// services/index.ts
export { MyService } from './my.service';

Still Not Working?

Check for circular dependencies. Two services that depend on each other can cause injection failures. Use forwardRef():

@Injectable({ providedIn: 'root' })
export class ServiceA {
  constructor(@Inject(forwardRef(() => ServiceB)) private b: ServiceB) {}
}

Check for multiple versions of Angular packages. Run npm ls @angular/core to ensure all packages use the same version.

Check your import paths. A service imported from the wrong path might not be the same class instance that is provided.

Check for SSR-only crashes. If NullInjectorError only appears in production logs and never in ng serve, the missing provider is in your SSR bootstrap. Open main.server.ts and confirm every provide*() from app.config.ts is mirrored. The most common omission is provideHttpClient(withFetch()) for SSR. The full SSR troubleshooting path lives in Fix: Angular SSR not working.

Check for useFactory returning undefined. A factory provider that returns undefined (or null from an inject() call that itself failed) makes Angular treat the token as unprovidable. Wrap factory bodies in try/catch and log the actual return value during boot.

Check for Optional consumers being treated as required. If a service is decorated with @Optional() in the constructor but strictNullChecks is off in your TypeScript config, the consumer code may still dereference the missing dependency and you will see follow-on errors that hide the real NullInjectorError further up the trace.

Check that lazy-loaded modules do not re-import HttpClientModule. Each re-import creates a new injector with its own interceptor chain. Auth headers added in a root interceptor never reach a lazy module’s HttpClient instance, and code paths that expect them throw NullInjectorError for the missing token the interceptor injects. Move the imports to the root and use provideHttpClient(withInterceptors(...)).

For TypeScript type errors in Angular, see Fix: TypeScript cannot find name. For module resolution issues, see Fix: TypeScript Cannot find module.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles