Home › Frontend › NullInjectorError No Provider: Angular DI Fix
Intermediate 5 min · September 23, 2026

NullInjectorError No Provider: Angular DI Fix

Add providedIn root or provideHttpClient() to fix it.

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
Before you start⏱ 12 min
  • ✓Basic Angular components and services
  • ✓Familiarity with TypeScript decorators
  • ✓A runnable Angular CLI project
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • Angular's NullInjectorError means no injector in the hierarchy registered the token you're injecting, so DI has no recipe to build it
  • Default every service to @Injectable({ providedIn: 'root' }) so the root injector owns exactly one shared instance
  • In standalone apps add provideHttpClient() to app.config.ts providers; in NgModule apps import HttpClientModule once in AppModule
  • TestBed builds an isolated injector, so specs need HttpClientTestingModule or provideHttpClientTesting() to mirror the app
✦ Definition~90s read
What is Angular NullInjectorError?

Dependency injection is Angular's system for building objects and handing them their collaborators. Instead of a component calling new UserService(), it declares constructor(private svc: UserService) and the framework's injectors supply the instance. A provider is the recipe: a mapping from a token, usually a class, to instructions for creating the value. @Injectable({ providedIn: 'root' }) registers the recipe with the root injector.

★
Think of Angular's dependency injection like a company kitchen.

A providers array on a module, component, or bootstrap config registers it with that specific injector. The token, the recipe, and the injector level are the three facts behind every injection.

Injectors form a hierarchy. At the top sits the platform injector with platform-wide singletons, below it the root injector holding application singletons, then child injectors for lazy modules and routes, and at the leaves the element injectors owned by each component instance.

Resolution walks upward from the requesting component: element, parents, module or route context, root, platform. The first recipe found wins, so a provider low in the tree shadows the same token higher up. NullInjectorError, coded NG0201, fires only when the walk reaches the top with no recipe found anywhere.

Standalone components reshaped where recipes live without changing the walk itself. NgModule apps centralize root recipes in AppModule imports; standalone apps centralize them in the providers array of bootstrapApplication or app.config.ts, using functional providers like provideHttpClient() and provideRouter().

Lazy routes still create child injectors, and TestBed still builds an isolated injector per spec. The mental model survives every era: find the token, find which injector should own its recipe, and register it there exactly once.

Plain-English First

Think of Angular's dependency injection like a company kitchen. When a cook (your component) needs a tool, they don't forge it themselves; they ask the kitchen manager (the injector), who checks the equipment list (providers). NullInjectorError means the tool was never put on any equipment list, so the manager can't hand it over. The fix isn't yelling louder; it's registering the tool once on the right list. Write providedIn: 'root' and every kitchen in the building shares one copy.

You've just wired up a service, injected it into a component, hit save, and the app dies with NullInjectorError: No provider for UserService. Nothing looks wrong: the class exists, the import path resolves, the constructor signature is clean. Yet Angular insists it has no idea how to build the thing you just wrote.

This error is dependency injection telling you the truth. Angular never constructs injected classes with new directly. It consults a hierarchy of injectors, each holding provider recipes, and walks upward until it finds one that knows your token. When every level shrugs, you get this error with the infamous R3InjectorError bracket chain naming each dependent that failed.

The standalone-component era added a fresh twist. Apps bootstrapped with bootstrapApplication have no AppModule, so providers live in app.config.ts or the bootstrap providers array. The classic HttpClientModule import becomes provideHttpClient(), and TestBed specs need provideHttpClientTesting(). Half the NullInjectorError reports you'll see today are migration fallout from this shift.

This guide covers the full picture: how the injector hierarchy resolves tokens, when to use providedIn root versus providers arrays, how standalone providers differ from NgModule wiring, why HttpClient is the most common victim, and how to configure TestBed so specs pass without touching working app code.

How Angular's Injector Hierarchy Actually Resolves Tokens

Angular never calls new on your services directly. When a component declares constructor(private svc: UserService), the framework asks the component's element injector for the UserService token. If that injector has no recipe, the request bubbles to the parent injector, then the module or standalone context injector, then the root injector, then the platform injector. The first recipe found wins, and only when every level misses do you see NullInjectorError.

This bubbling design is what makes providedIn: 'root' so powerful. The decorator registers the recipe with the root injector at build time, so every component in the app resolves the same shared instance without any module editing. Tree-shaking also benefits: if nothing injects the service, the compiler drops it from the bundle. You get a singleton and dead-code elimination from one line.

The bracket chain in R3InjectorError mirrors this walk. Standalone[CheckoutComponent] names the injector where resolution started, and each arrow names a dependent that needed the next token. Reading right-to-left shows the exact missing recipe. Most developers read left-to-right and blame the component, but the component is just the messenger. The fix always targets the rightmost token: register it where the failing subtree can see it, and everything left of the arrow resolves itself.

src/app/user.service.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class UserService {
  private users = ['ana', 'ben', 'cy'];
  list(): string[] {
    return this.users;
  }
}

// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-list',
  standalone: true,
  template: `<ul><li *ngFor="let u of users">{{ u }}</li></ul>`,
})
export class UserListComponent implements OnInit {
  users: string[] = [];
  constructor(private usersSvc: UserService) {}
  ngOnInit(): void {
    this.users = this.usersSvc.list();
  }
}
Try it live
📊 Production Insight
In production logs the chain often appears truncated by error reporters. Log the full error.message, not just the first line, or you'll chase the component instead of the missing token.
🎯 Key Takeaway
Injection requests bubble from element to root injector; the first recipe wins, and total failure produces NullInjectorError naming the missing rightmost token.

providedIn root vs providers Arrays: Picking One Owner

New Angular developers often register services in two places at once: providedIn: 'root' on the class plus an entry in some module's providers array. For eager modules this duplication is harmless because both point at the root injector. For lazy modules it silently forks the service: the lazy child injector builds its own instance, and state written through one route is invisible through another. Login works, then the header says logged out.

The rule is one owner per service. Shared state like auth, cart, and API clients belongs to the root injector via providedIn: 'root', with zero providers entries anywhere. Deliberately scoped state, like a wizard's draft data, belongs in the providers array of the component or lazy route that owns it. Document the scoping choice with a comment, because the next developer will otherwise hoist it to root during cleanup and reintroduce the bug in reverse.

Choosing wrong also affects bundle size and testability. Root-provided services tree-shake when unused and inject cleanly into any TestBed. Component-scoped services can't be injected outside their subtree, which makes cross-component tests awkward. When a NullInjectorError names your own service token, check for double registration before adding yet another providers entry. Removing the extra registration beats adding a third.

src/app/cart.service.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Injectable } from '@angular/core';

// Option A: root singleton, preferred for shared services.
@Injectable({ providedIn: 'root' })
export class CartService {
  items: string[] = [];
}

// Option B: scoped instance, one per component subtree.
import { Component } from '@angular/core';

@Component({
  selector: 'app-cart',
  standalone: true,
  providers: [CartService],
  template: `<p>{{ items.length }} items</p>`,
})
export class CartComponent {
  items: string[] = [];
  constructor(private cart: CartService) {}
  ngOnInit(): void {
    this.items = this.cart.items;
  }
}

// providedIn: 'root' needs NO entry in any providers array.
// Adding one in a lazy module forks a second instance.
Try it live
📊 Production Insight
Double registration causes state-split bugs with zero errors in the console. If users report settings that won't stick across routes, grep providers arrays before suspecting the backend.
🎯 Key Takeaway
Give every service exactly one owner: providedIn root for shared singletons, a local providers array only for deliberately scoped state.

Standalone Apps: provideHttpClient and importProvidersFrom

Standalone apps have no AppModule, so there is no imports array at the root that can carry provider registrations. Instead, bootstrapApplication takes a providers array, conventionally factored into app.config.ts. Functional providers like provideRouter() and provideHttpClient() register the same recipes that NgModules used to contribute, but they're tree-shakable: unused features never enter the bundle.

The migration trap is assuming old module imports still feed the root injector. A shared UI module that imports HttpClientModule registers that recipe in its own branch, not in the standalone bootstrap context. Your product page may work because it resolves through that branch, while checkout, served from a different branch, throws NullInjectorError for HttpClient. Both observations are true simultaneously, which is why the bug survives code review.

The bridge for half-migrated apps is importProvidersFrom(), which pulls an NgModule's providers into the standalone bootstrap context. Use it for third-party modules that haven't shipped functional providers yet, and prefer provideHttpClient() for first-party HTTP. Audit every module import you delete during migration: if the module registered providers, its deletion removes recipes, and each one needs a functional replacement in app.config.ts.

src/app/app.config.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

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

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig).catch((err) =>
  console.error(err),
);

// Legacy bridge when a module hasn't migrated yet:
// providers: [importProvidersFrom(OldModule)]
Try it live
📊 Production Insight
During migration, deleting a module import deletes its provider recipes too. Diff deleted imports against added functional providers one by one before merging.
🎯 Key Takeaway
Standalone bootstrap has no root module, so every provider recipe must appear in app.config.ts via functions like provideHttpClient() or importProvidersFrom().

The Missing HttpClient Provider: DI's Most Common Victim

HttpClient is the most common NullInjectorError victim because its recipe never comes from your code. No decorator on your service can conjure it; only HttpClientModule in an NgModule imports array or provideHttpClient() in a standalone providers array registers it. Developers write a perfect root-provided service, inject HttpClient, and blame their own decorator when the real gap is the missing HTTP registration two files away.

The error chain gives it away: YourService -> HttpClient -> HttpClient ends in the framework token, not your class. That trailing repetition means Angular resolved your service fine and failed one level deeper. The fix never touches your service file. It touches app.module.ts or app.config.ts, which is exactly why the bug feels misplaced.

Prevention is a startup smoke test. A spec that injects HttpClient through your real appConfig fails the moment someone drops the registration, long before checkout breaks in production. Also centralize HTTP behind one or two root-provided API services instead of injecting HttpClient in twenty components. Fewer injection sites mean fewer chains to read, and interceptors attach in one place. When every request flows through a single service, a missing HttpClient provider breaks one obvious test instead of twenty mysterious components.

src/app/checkout.service.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Order {
  id: string;
  total: number;
}

@Injectable({ providedIn: 'root' })
export class CheckoutService {
  constructor(private http: HttpClient) {}
  placeOrder(order: Order): Observable<Order> {
    return this.http.post<Order>('/api/orders', order);
  }
}

// app.config.ts must include provideHttpClient()
// or this service throws: No provider for HttpClient.
// NgModule apps instead import HttpClientModule once in AppModule.
Try it live
📊 Production Insight
Centralize HTTP behind one root-provided API service. One injection site turns a missing provider into a single loud failure instead of twenty scattered ones.
🎯 Key Takeaway
HttpClient's recipe comes only from HttpClientModule or provideHttpClient(); a chain ending in HttpClient means your service is fine and the HTTP registration is missing.

TestBed Injectors: Why Tests Fail When the App Works

TestBed builds a brand-new injector that inherits nothing from your app. A spec with an empty configureTestingModule has no HttpClient recipe, no router, and none of your root services unless you list them. The app can run perfectly while its specs fail with NullInjectorError, and the correct response is fixing the spec, not rewiring working app code.

For HTTP graphs the modern pair is provideHttpClient() plus provideHttpClientTesting(), which swaps the real backend for HttpTestingController. You assert requests with expectOne and flush fake responses, so specs never touch the network. The legacy equivalent is importing HttpClientTestingModule. Both approaches keep the testing injector shaped like the app injector without duplicating app.config.ts by hand.

For component specs, prefer NO_ERRORS_SCHEMA-free setups that declare the real dependency graph with mocks only at the boundary. Mock the service your component injects, not HttpClient three levels down: providers: [{ provide: CheckoutService, useValue: fakeService }]. This keeps specs fast and focused, and a NullInjectorError in the spec then points at the component's direct dependency instead of a framework token. Run the single spec file in isolation before editing anything; a green isolated run proves the failure was injector setup, not logic.

src/app/checkout.service.spec.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { TestBed } from '@angular/core/testing';
import {
  provideHttpClient,
  provideHttpClientTesting,
  HttpTestingController,
} from '@angular/common/http/testing';
import { CheckoutService } from './checkout.service';

describe('CheckoutService', () => {
  let svc: CheckoutService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [provideHttpClient(), provideHttpClientTesting()],
    });
    svc = TestBed.inject(CheckoutService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => httpMock.verify());

  it('posts an order', () => {
    svc.placeOrder({ id: 'a1', total: 42 }).subscribe();
    const req = httpMock.expectOne('/api/orders');
    expect(req.request.method).toBe('POST');
    req.flush({ id: 'a1', total: 42 });
  });
});
Try it live
📊 Production Insight
Never edit app providers to satisfy a red spec until the spec runs green in isolation with testing providers. Most test-only NullInjectorErrors are missing testing modules, not app bugs.
🎯 Key Takeaway
TestBed injectors start empty, so mirror the app's HTTP registration with provideHttpClientTesting() and mock services at the component boundary.

Reading the Bracket Chain: R3InjectorError Token Paths

The R3InjectorError format looks hostile but parses mechanically. R3InjectorError(Standalone[CheckoutComponent]) names the injector where resolution started. Each bracketed segment TypeA -> TypeB means TypeA's constructor needed TypeB. The final NullInjectorError: No provider for HttpClient names the token nobody registered. Fix that token and the whole chain collaps to resolved.

Three chain shapes cover nearly everything. A chain ending in your own service means the service lacks providedIn or a providers entry. A chain ending in HttpClient, ActivatedRoute, or another framework token means a module import or functional provider is missing at the owning level. A chain naming a component token means you're injecting across element-injector branches, usually a sibling, and need to restructure with @Input() or a shared root service.

Build a team habit around chains: paste the full chain into the bug report, name the rightmost token explicitly, and state which injector level should own it. Reports written this way get fixed in one pass because the diagnosis is already half done. Reports that say only checkout is broken invite a week of guessing. The chain is the framework handing you the answer; reading it right-to-left is the whole skill.

💡Read the Chain Right-to-Left
Copy the chain, find the last token before the closing bracket, and grep for its registration. That single habit resolves most NullInjectorError reports in under five minutes.
📊 Production Insight
Require full chains in bug reports. A report naming the rightmost token gets fixed in one pass; a report saying checkout is broken invites days of guessing.
🎯 Key Takeaway
Parse chains right-to-left: the start names the failing injector, each arrow names a dependent, and the final token is the recipe you must register.
● Production incidentPOST-MORTEMseverity: high

The Standalone Migration That Broke Checkout for 38 Minutes

Symptom
Checkout clicks started failing at 2:04 PM right after the migration deploy. The console showed NullInjectorError: R3InjectorError(Standalone[CheckoutComponent])[CheckoutService -> HttpClient -> HttpClient]. Product pages rendered fine because they never injected HttpClient. The error rate on the checkout API route hit 100% while every other dashboard stayed green.
Assumption
The team assumed standalone migration was mechanical: move declarations to imports arrays and delete NgModules. The migration guide mentioned provideHttpClient() but the diff reviewer read it as optional cleanup, since HttpClientModule still appeared in a shared UI module's imports. Nobody traced which injector actually served the checkout route.
Root cause
The migration deleted AppModule, which had imported HttpClientModule and registered the HttpClient recipe in the root injector. The new bootstrapApplication call in main.ts passed only routing providers, and the shared UI module's HttpClientModule import served a different injector branch that the checkout service never consulted. Any service injecting HttpClient threw NullInjectorError: No provider for HttpClient the moment checkout loaded.
Fix
The on-call engineer added provideHttpClient() to the providers array in app.config.ts, redeployed, and checkout recovered in nine minutes. The follow-up removed HttpClientModule from the shared UI module to leave a single registration, added an app-start smoke test that injects HttpClient through the real bootstrap path, and made provideHttpClient() a checklist item in the migration runbook.
Key lesson
  • Standalone migration isn't mechanical: every NgModule import that registered providers needs a functional equivalent like provideHttpClient() in the bootstrap providers, or its recipe silently vanishes.
  • A shared module's imports don't feed the root injector of a bootstrapped standalone app, so 'it's imported somewhere' is never proof the token resolves on the failing route.
  • Smoke-test the real bootstrap path in CI: a five-line test that injects HttpClient through appConfig would have failed the migration PR before it ever reached production.
Production debug guideFive injector failures you'll meet in production, with the exact check that names each one.5 entries
Symptom · 01
Console shows R3InjectorError with a bracket chain like [Service -> Token -> Token]
→
Fix
Copy the full chain from the console. Read it right-to-left: the last token before the closing bracket is the missing recipe. Run grep -rn "provide.TokenName\|TokenName.providers\|providedIn" src/ to find every registration. If grep finds nothing, register the token at root. If grep finds it in a lazy module but the failing component is outside that module, move the registration up to root or app.config.ts.
Symptom · 02
Chain ends in HttpClient after a standalone migration or a new service
→
Fix
Open the chain: if it reads YourService -> HttpClient -> HttpClient, the app never registered the HTTP recipe. For NgModule apps, open app.module.ts and confirm HttpClientModule is in imports (not providers). For standalone apps, open app.config.ts or main.ts and confirm provideHttpClient() is in providers. Add the missing piece, restart ng serve, and confirm the chain disappears on reload.
Symptom · 03
Works on one route, throws on another after lazy loading
→
Fix
Note which routes work and which fail. Open the lazy feature module or the route's providers and search for the token. If it sits in a lazy providers array while the failing component lives elsewhere, that's your scoping bug. Move shared services to providedIn root and reserve lazy providers for deliberately scoped state. Verify by navigating root route to lazy route and back.
Symptom · 04
App runs fine but ng test fails with the same missing token
→
Fix
Keep app code untouched and open the .spec.ts. A bare TestBed.configureTestingModule({}) inherits nothing from the app injector. Add HttpClientTestingModule to imports or providers: [provideHttpClient(), provideHttpClientTesting()]. Re-run only that spec with ng test --include. If it passes, the bug was test-only and production code was always correct.
Symptom · 05
Error persists no matter where you add the module, with hints about invalid providers
→
Fix
Open the file where you added the module and check which array holds it. Modules belong in imports; services, values, and factory functions belong in providers. If a module class sits in providers, remove it and add the correct entry: HttpClientModule to imports, or provideHttpClient() to providers. Restart the dev server so the injector graph rebuilds from clean state.
NullInjectorError Causes Compared
Root CauseHow to ConfirmFixPrevention
No provider registered for the token anywhereError chain ends in the bare token and grep finds zero providers entries or providedIn for itAdd @Injectable({ providedIn: 'root' }) or list the class in a providers arrayDefault every service to providedIn root; lint for injectables missing it
HttpClient never providedChain shows Service -> HttpClient -> HttpClient with no HttpClientModule import or provideHttpClient callModule apps: import HttpClientModule in AppModule; standalone: add provideHttpClient() to app.config.tsAdd an app-start smoke test that injects HttpClient in CI
Provider registered in the wrong injectorWorks on one route but fails on another; the provider lives in a lazy module instead of rootMove the provider to root or scope it deliberately per lazy injectorKeep a providers inventory per module; review lazy-module providers in PRs
TestBed missing the providerApp runs but ng test fails with the same token missingImport HttpClientTestingModule or call provideHttpClientTesting() in the specScaffold specs with testing modules included; run specs in CI on every PR
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
srcappuser.service.ts@Injectable({ providedIn: 'root' })How Angular's Injector Hierarchy Actually Resolves Tokens
srcappcart.service.ts@Injectable({ providedIn: 'root' })providedIn root vs providers Arrays
srcappapp.config.tsexport const appConfig: ApplicationConfig = {Standalone Apps
srcappcheckout.service.tsexport interface Order {The Missing HttpClient Provider
srcappcheckout.service.spec.tsprovideHttpClient,TestBed Injectors

Key takeaways

1
NullInjectorError means no injector in the hierarchy holds a recipe for the token; register it with providedIn root or a providers entry.
2
Default services to @Injectable({ providedIn
'root' }) so one shared instance lives in the root injector.
3
Standalone apps provide HttpClient with provideHttpClient() in app.config.ts, not HttpClientModule in imports.
4
A lazy module's providers entry forks a second instance; keep singletons in root unless you want scoping.
5
TestBed builds an isolated injector, so specs need HttpClientTestingModule or provideHttpClientTesting().
6
Read the R3InjectorError chain right-to-left
the last token is the missing recipe to register.

Common mistakes to avoid

5 patterns
×

Registering the same service in providedIn root and a lazy module's providers

Symptom
No error at all, which is worse: two instances exist, state written on one route is invisible on another, and the bug report reads like stale cache.
Fix
Register the service once at the level that matches its scope: providedIn root for shared singletons, a lazy module's providers only when you truly want a second instance. Remove the duplicate and re-test both routes.
×

Putting HttpClientModule in providers instead of imports

Symptom
NullInjectorError for HttpClient survives every edit, sometimes joined by a second error about an invalid provider, because a module class is not a valid provider recipe.
Fix
Module apps: import HttpClientModule in AppModule. Standalone apps: add provideHttpClient() to the providers in app.config.ts or bootstrapApplication. Never list an HttpClientModule class inside a providers array.
×

Importing HttpClientModule in a lazy feature module instead of the root

Symptom
Root components inject HttpClient fine but a lazy route's component throws NullInjectorError for HttpClient even though the module appears in some imports array.
Fix
Move the import to the injector that owns the failing component: AppModule imports for module apps, app.config.ts providers with provideHttpClient() for standalone apps. Re-run and confirm the token chain is gone.
×

Testing an HttpClient service with an empty TestBed

Symptom
ng test fails with NullInjectorError for HttpClient while the app runs fine, and developers start editing working providers to satisfy a test-only injector.
Fix
Configure the testing injector explicitly: TestBed.configureTestingModule with HttpClientTestingModule or providers [provideHttpClient(), provideHttpClientTesting()]. Run the single spec before touching app code.
×

Injecting a component-scoped token from a sibling subtree

Symptom
NullInjectorError names a component or directive token you never registered anywhere, because providers on one branch of the element tree are invisible to sibling branches.
Fix
Inject the token where it is visible: move shared state to a root-provided service and pass data down with @Input(). Read the bracket chain in the error to find which injector boundary you crossed.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does NullInjectorError: No provider for X mean?
Q02SENIOR
How does Angular's injector hierarchy resolve a token?
Q03SENIOR
Why does HttpClient need provideHttpClient() in standalone apps?
Q04SENIOR
How does a lazy module duplicate a root singleton, and how do you prove ...
Q05SENIOR
How do you design TestBed providers for a component with injected servic...
Q01 of 05JUNIOR

What does NullInjectorError: No provider for X mean?

ANSWER
It means Angular's dependency injection tried to build an object but no injector in the hierarchy had a provider recipe for one of the requested tokens. The fix is registering the token: @Injectable({ providedIn: 'root' }) on a service, a providers entry, or the right module import like HttpClientModule or provideHttpClient().
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Should I use both providedIn root and a providers entry?
02
Can I just use new MyService() instead of TestBed?
03
What's the difference between providedIn root and component providers?
04
Do lazy-loaded modules get their own injector instances?
05
How do I read the R3InjectorError bracket chain?
06
Could NullInjectorError be a framework bug?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
🔥

That's Angular. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
Vue Avoid Mutating a Prop Directly
1 / 6 · Angular
Next
Angular Can't Bind to ngModel: FormsModule Fix
→