NullInjectorError: No provider for CoursesService!

Understanding the "No Provider" Error

The error message you see is Angular's way of telling you that it cannot create the dependency you are trying to inject. This happens because the dependency injection system has no information about how to instantiate that particular class. It lacks a so-called provider.

Without a provider, Angular simply does not know which function to call to create an instance of CoursesService. The system relies on providers to map a dependency to a factory function that can produce it.

This error is common during development, especially when you forget to register a service in a module or component. But to debug it effectively, you need to understand what a provider really is under the hood.

What is a Provider in Angular Dependency Injection?

A provider is essentially a recipe for creating a dependency. It tells Angular exactly how to construct an instance of a class when that class is requested as an injection target.

In its simplest form, a provider is a plain function that Angular can invoke to produce the dependency. This function is called a provider factory function. It takes any required inputs, invokes the appropriate constructor, and returns the fully initialized instance.

While Angular can generate this function automatically for most cases using its default conventions, you have the option to write it manually when you need fine-grained control. Either way, every dependency in your application has an associated factory function somewhere, even if you never see it.

Writing Your Own Provider Manually

To see this in action, let's write a provider factory function for the CoursesService class ourselves:

This function receives whatever dependencies CoursesService needs, calls its constructor with those arguments, and returns the resulting instance.

Now, whenever Angular needs a CoursesService instance, it can simply call this function. However, there is a catch: Angular does not yet know this function exists, and even if it did, it would not know to associate it with the specific injection request for CoursesService.

So how do we bridge that gap? How does Angular know which factory to call for which dependency?

Introduction to Injection Tokens

To resolve this ambiguity, Angular uses injection tokens. An injection token is a unique identifier that classifies a particular type of dependency. It allows Angular to distinguish between different kinds of dependencies and match them with the correct provider.

Think of it as a label that says: "this token represents dependencies of this exact category." You can create such a token manually for our CoursesService:

This token object is unique by nature — unlike a string, there is no risk of collision. The same token is used to mark both the dependency request and the provider that satisfies it.

But how do we actually use this token to wire everything together?

How to Manually Configure a Provider

With both the provider factory function and the injection token in hand, we can now register a provider in Angular's dependency injection system. The provider is a configuration object, placed in the providers array of a module or component:

This manually configured provider contains three key parts:

  • useFactory: a reference to the provider factory function that Angular will call when it needs to create the dependency.
  • provide: the injection token that links this provider to a specific dependency type.
  • deps: an array of any additional dependencies that the factory function needs to run, such as the HTTP client.

Now, we might think that this is enough. But if we try to inject CoursesService again, we still run into the same problem:

NullInjectorError: No provider for CoursesService!

We defined the provider, but Angular still does not know that this provider should be used for that particular injection request. The link between the dependency and the provider is missing.

To make the connection explicit, we need to use the @Inject decorator wherever CoursesService is injected:

By adding @Inject(COURSES_SERVICE_TOKEN), we tell Angular: "for this dependency, use the provider associated with this specific token."

The injection token is what bridges the gap. It tells Angular which provider factory to call. Once this link is in place, Angular can create the dependency correctly, and the application runs without errors.

Now that you have a clear picture of how the system works under the hood, you might be thinking: "This seems like a lot of manual work just to inject a simple service." And you would be right — but this is the foundation. In the next section, we will explore how Angular simplifies all of this with its default conventions, so you rarely need to go through these steps by hand.

Why is manual provider configuration rarely necessary?

Although it might seem like you rarely need to set up provider factories or injection tokens by hand, that is precisely what is taking place behind the scenes, in every single case.

For each dependency in your application — a service, a component, or any other type — there is always a provider and a corresponding injection token (or an equivalent mechanism) that uniquely identifies that dependency.

This is unavoidable, since something in your system has to invoke the constructors of your classes, and Angular must know exactly what to instantiate.

So, even when your dependency configuration looks simple and declarative, a provider is always implicitly present.

To make this clearer, let's strip down our provider definition step by step until we arrive at the familiar, simplified syntax you use every day.

Using class names as injection tokens

A particularly powerful aspect of Angular's dependency injection is that you are not limited to explicit token objects — you can use any value that is guaranteed to be unique in the Javascript runtime.

Take class names: at runtime, a class is represented by its constructor function, and that function reference — such as its name — is guaranteed to be unique.

Because of this guaranteed uniqueness, the constructor function itself can serve as an injection token.

We can therefore simplify our provider by removing the manually created token:

As shown, we no longer rely on the COURSES_SERVICE_TOKEN object we created earlier to identify the dependency. In fact, we've removed that object entirely from our code, since for service classes, the class name itself is sufficient to identify the dependency.

However, if we run the program as-is, we will encounter the familiar "no provider" error again.

To resolve this, we need to use the CoursesService constructor function to specify which dependency we want:

This tells Angular exactly what to inject, and everything works as expected. 😉

The good news is that in most cases, manually creating an injection token is unnecessary.

Now, let's see how we can simplify our provider even further.

Simplifying provider configuration: useClass

Instead of writing a factory function with useFactory, Angular provides other, more convenient ways to specify how a dependency should be instantiated.

For our provider, the useClass property is ideal.

When useClass is used, Angular knows the value is a valid constructor function and can invoke it with the new operator:

This significantly reduces the boilerplate, since we no longer need to write a manual factory function. 👍

Another major benefit of useClass is that Angular can infer the injection token at runtime based on the Typescript type annotations.

This means that with useClass, the Inject decorator is no longer required — which explains why you rarely see it in practice:

How does Angular know which dependency to inject? It inspects the type of the injected property — in this case CoursesService — and uses that type to locate the appropriate provider.

As we can see, class-based dependencies are far more convenient than having to explicitly use @Inject. 👍

For useClass providers, there is yet another level of simplification.

Rather than defining a provider object manually, you can pass the class name directly as a valid provider configuration:

Angular recognizes this as a constructor function, inspects its dependencies, creates a factory function automatically, and instantiates the class on demand.

All of this happens implicitly, just from the function name.

This is the default notation you'll use in most cases — simple and easy. 😉

With this shorthand, you might not even realize that providers and injection tokens are working behind the scenes.

But note: simply adding a provider like this is not enough. Angular still needs to know how to resolve the dependencies of the class (remember the deps property).

For this to work, the service class must be decorated with Injectable():

This decorator instructs Angular to inspect the constructor argument types at runtime and resolve the dependencies accordingly.

So this simplified notation is how we typically interact with Angular's dependency injection, without having to think about the underlying machinery. 😉

One important caveat: useClass does not work with interface names — it works only with classes.

Interfaces are a compile-time-only feature of Typescript; they simply do not exist at runtime. Therefore, unlike a class name (via its constructor function), an interface name cannot uniquely identify a dependency type.

In addition to the basics — providers, dependencies, and injection tokens — there are a few more key concepts to keep in mind about Angular's dependency injection.

Understanding Angular multiple value dependencies

Most dependencies in your system will map to a single value, such as a class instance.

However, there are cases where you need a dependency that holds multiple values.

One common example is form control value accessors.

These are directives that bind to a form control and expose its value to the Forms module.

There isn't just one such directive — there are several built-in ones, and you might add your own.

Configuring each of these individually would be impractical, since you typically need to access them all together.

The solution is a special type of dependency that accepts multiple values, all tied to the same injection token.

For form control value accessors, that token is NG_VALUE_ACCESSOR.

Here is an example of a custom form control component registering itself as a value accessor:

Note that we are providing the NG_VALUE_ACCESSOR token here.

If we didn't use the multi property, we would overwrite the existing value for this token (we'll discuss this shortly).

But with multi: true, we are appending to the existing array of values for this dependency, rather than replacing it.

Any component or directive that needs access to all control value accessors can request the NG_VALUE_ACCESSOR token.

They will receive an array that includes all the standard accessors along with our custom one.

When to use a useExisting provider

Notice the useExisting option in the example above.

This option is handy when you want to define a provider in terms of another existing provider.

Here, we create a provider by simply referencing the ChooseQuantityComponent class name, which, as we've learned, can act as a provider.

useExisting is also useful for creating an alias — an alternative name — for an existing provider.

Now that we have a solid grasp of providers and injection tokens, let's turn to another fundamental concept: the hierarchical nature of Angular's dependency injection.

Understanding Angular Hierarchical Dependency Injection

Unlike its AngularJs predecessor, Angular's dependency injection system is hierarchical.

What does that mean concretely?

In Angular, you have several places to register providers:

  • at the module level
  • at the component level
  • even at the directive level

What's the difference between these levels, how does it work, and why are these options available?

You can define providers at multiple levels because the dependency injection system is hierarchical.

When a dependency is needed — for example, when a component asks for a service — Angular first looks in the provider list of that component itself.

If no provider is found there, Angular moves up to the parent component.

If a provider is found, it's used; otherwise, Angular continues up to the parent of that parent, and so on.

This search continues until reaching the root component.

If no provider is found by then, we meet our old friend: the "No provider found" error. 😉

This upward traversal of the component tree in search of a provider is called dependency resolution, and because it follows the component hierarchy, Angular's DI is described as hierarchical.

It's also worth understanding why this design is beneficial.

What are the advantages of Hierarchical Dependency Injection?

Angular is often used to build large-scale applications, which can become quite complex.

To manage this complexity, applications are typically split into small, well-encapsulated modules, each composed of a structured tree of components.

Different sections of the page may require certain services and dependencies, some of which should be shared, and some of which should remain private.

Consider a self-contained section of the page that operates independently, using its own instances of particular services and dependencies.

We want those dependencies to stay private, unreachable by the rest of the application, to prevent bugs and ease maintenance.

Some services used by this section might be shared with other parts of the app or with parent components higher up the tree, while others are meant to be isolated.

A hierarchical DI system supports exactly that. 👍

It allows you to isolate sections of the app with their own private dependencies, allows parent components to share certain dependencies with their children only, and lets you decide exactly what is shared and what is not.

This makes your application more modular and maintainable, letting you share dependencies between different parts of the system only when necessary.

This overview provides a solid foundation, but to truly grasp how it all fits together, we need a complete, concrete example.

Exploring Hierarchical Dependency Injection with a Practical Example

Let's take our CoursesService class and think about what happens when we inject it at various levels of an Angular component tree.

Do we end up with one shared instance across the entire tree, or does each injection point receive a fresh copy? How is this determined?

To get a clear picture, let's assign a unique identifier to each CoursesService instance. This way, we can trace exactly what's happening when the application runs.

We'll set up a simple component hierarchy and place CoursesService injections in several spots to observe the outcome.

The setup includes a root application component whose template renders child course-card components.

Let's inspect the template for the root component, app.component.html:

This template shows that the root component uses an ngFor loop to render several course-card components internally.

Now, look at the component class in app.component.ts:

It's important to note that we're registering CoursesService in the providers array of the root component. We're also logging the unique id of the service instance that gets injected.

Next, let's see the course-card component definition, located in course-card.component.ts:

Here, CoursesService is also added to the component's providers list, and we log the id of its service instance as well.

Now, before we run the application, what's your guess?

How many separate CoursesService instances will be created, and which specific instance will each course-card component receive?

Here's the console output after running the app:

App component service Id = 1
course card service Id = 2
course card service Id = 3
course card service Id = 4
course card service Id = 5
course card service Id = 6
course card service Id = 7
course card service Id = 8
course card service Id = 9
course card service Id = 10
course card service Id = 11

Let's break down what we observe.

It appears the application root component, app.component.ts, created a service instance first, getting Id = 1.

When the root component needed CoursesService, it examined its own providers list first, found a match, and used that instance.

But what's happening inside the course-card component?

Remember, the application displays 10 instances of course-card.

Each one of these component instances required its own CoursesService. To fulfill this, each instance checked its own providers list.

Every course-card instance found a provider within its own private list, which allowed it to instantiate a brand new CoursesService and inject it.

This means each course-card instance created its own solitary service object, without needing to request one from the parent root component.

Consequently, there are 10 distinct instances of course-card, each owning a private CoursesService, which clarifies the log output.

These private service instances are tightly coupled to the lifecycle of their respective course-card component.

The moment a specific course-card component is destroyed, its associated CoursesService becomes eligible for garbage collection.

However, CoursesService is mostly stateless (apart from our simple counter), so creating so many copies isn't really necessary.

Ideally, we'd have just one instance of CoursesService, created at the root level and shared down to all child components.

To achieve this, we can simply remove the CoursesService from the providers array of CourseCardComponent:

Notice the component now has an empty providers list; in fact, we could remove the providers property entirely.

If we run the application again, the output looks like this:

App component service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1

The application now correctly uses a single CoursesService instance. 👍

We've now seen how hierarchical dependency injection operates within the component tree specifically — Angular first checks the component's own providers, then scans all parent components.

However, what about modules? They can also declare their own providers.

Understanding Modules Hierarchical Dependency Injection via a Practical Example

Let's keep our components as they are now:

  • the root component AppComponent has a CoursesService provider
  • the course-card component has no private providers

It's important to mention that course-card is part of a CoursesModule.

What happens if we retain the current component providers and introduce two additional providers at the module level?

First, we'll add a provider to CoursesModule:

This is a feature module that's imported into the application's root module.

Next, let's place another provider at the root module level:

This brings our total to three providers:

  • 1 provider on the root component AppComponent
  • 1 provider on the feature module CoursesModule
  • 1 provider on the root module AppModule

Predict what will happen when we run the application now.

The console shows:

App component service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1

The output is precisely the same as before!

Why is that?

The Distinction Between Modules and Components DI Hierarchies

The reason is that Angular maintains two separate dependency injection hierarchies:

  • a component hierarchy that follows the component tree structure on the page
  • a separate module-level injection hierarchy

Furthermore, the component hierarchy has precedence over the module hierarchy! 😉

When Angular looks for a dependency, it first attempts to resolve it using providers from the component tree.

If no match is found after traversing from the current component up to the root component, only then does Angular check the module hierarchy.

At that point, it starts with the providers of the current module. If there's no match there, it moves up to the parent module, and so on, until it reaches the application's root module.

This dual-hierarchy system allows you to modularize your application along two separate dimensions:

  • you can make dependencies available to a particular section of the component tree using component-level providers

  • you can create isolated versions of a service that are specific to certain modules using module-level providers

With a solid grasp of the core concepts, we can now dive into fine-tuning the resolution process.

Fine-Tuning the Dependency Resolution Process

As we've seen, the standard component DI resolution starts at the current component and scans upward through all parent components, ending at the root. If no match is found, Angular throws an error.

What if we need to alter this default behavior?

The @Optional Decorator

Imagine a scenario where the dependency might not be available, and the component can function without it or has a fallback plan. In this case, we might want to prevent the error from being thrown.

The @Optional decorator is the solution. By marking a dependency as optional, no error will occur if a provider isn't found. However, the component must handle the null case gracefully.

The @SkipSelf Decorator

We can also adjust the starting point of the search for a provider.

Consider a situation where a component provides an instance of a service for its children but needs a different instance from its own ancestors for itself.

Although this is a rare use case, 😃 it's still supported.

By using @SkipSelf, Angular will ignore the component's own providers and begin its search from the parent component, traversing upwards to the root.

In our setup, this means the local CoursesService provider on CourseCardComponent is skipped, making it available only to child components of course-card.

Running the application with this change yields the following log:

App component service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1
course card service Id = 1

The provider on CourseCardComponent was ignored, and the one at CoursesModule level was selected.

The @Self Decorator

We can also control where the search process stops.

For instance, if a component should only use its own providers and not check its parents, we can use the @Self decorator.

In this case, the component's own CoursesService provider is used, while the one at the CoursesModule level is overlooked.

The corresponding console log is:

App component service Id = 1
course card service Id = 2
course card service Id = 3
course card service Id = 4
course card service Id = 5
course card service Id = 6
course card service Id = 7
course card service Id = 8
course card service Id = 9
course card service Id = 10
course card service Id = 11

This brings us back to a scenario where each component instance has its own private service instance.

The @Host Decorator

We've focused on components, but what about directives?

Since components are essentially a type of directive, all the principles we've covered apply to directives as well.

However, there's a particular scenario: what if we have a directive tightly coupled to a specific component and it needs to access that component's private service instance?

For example, imagine a HighlightedDirective designed to visually enhance a course card. This directive is meant to work closely with CourseCardComponent and needs its private CoursesService.

The directive can reach that private service using the @Host decorator:

The @Host decorator controls where the search ends, similar to @Self, but it's intended for directives. It instructs Angular to look for a matching provider only on the host component of the directive.

Let's apply this directive to each course-card instance:

The highlighted attribute adds a companion HighlightedDirective to each CourseCardComponent instance.

After running the application, the console shows:

App component service Id = 1
course card service Id = 2
coursesService highlighted 2
course card service Id = 3
coursesService highlighted 3
course card service Id = 4
coursesService highlighted 4
course card service Id = 5
coursesService highlighted 5
course card service Id = 6
coursesService highlighted 6
course card service Id = 7
coursesService highlighted 7
course card service Id = 8
coursesService highlighted 8
course card service Id = 9
coursesService highlighted 9
course card service Id = 10
coursesService highlighted 10
course card service Id = 11
coursesService highlighted 11

Each HighlightedDirective correctly gets the private service instance from its host component.

With this, we've now gone through all the available options for customizing the DI resolution process.

Now that we have a thorough understanding of hierarchical DI, let's explore another powerful feature of Angular's dependency injection system.

What Are Tree-Shakeable Providers?

The providers we've examined up to this point lack a crucial characteristic: they aren't tree-shakeable.

What does that actually entail?

All the providers we've discussed so far were manually added to the providers array of a component or module.

This approach, however, comes with a practical drawback.

Consider a scenario where an application relies on a module, which in turn imports another module that provides a service class.

Now, suppose that imported service class isn't actually utilized anywhere in our application, for one reason or another.

This situation is quite common in practice.

Take AngularFire or other large third-party packages as an example. These modules bundle a wide range of services that you might need in your app—or might not.

You'll likely use a handful of these services, but probably not all of them.

The goal is to have the ability to import a module while excluding unused services from our production bundle.

How can we accomplish this?

What is Tree Shaking?

During the build process, the Angular CLI strives to "tree shake" unnecessary code out of the bundle to keep its size as small as possible.

It does this by statically analyzing the TypeScript dependencies in our codebase to determine whether a dependency is actively referenced.

If a dependency doesn't appear to be used, the CLI removes it from the bundle, thereby reducing the overall file size.

However, when we place a module containing unused services into the providers array of a module or component, we first have to import that module or service via a direct TypeScript import.

This TypeScript import effectively blocks the tree-shaking mechanism from discarding the unused service.

The tree shaker observes that the service is imported explicitly through TypeScript, assumes it's being used, and retains it in the production bundle.

So, what's the solution?

The answer lies in tree-shakeable providers. 😉

Understanding Tree-Shakeable Providers with an Example

Our objective is to define the CoursesService within the CoursesModule, which serves as a feature module.

This could just as easily be a third-party module that we've brought into our application.

We aim to establish a provider for the CoursesService such that it gets bundled only when someone importing CoursesModule actually uses the service.

Conversely, if an application imports CoursesModule without ever touching the CoursesService, the service should not be part of the bundle.

To achieve this, the initial step is to remove the CoursesService from the CoursesModule's provider list:

But won't we now run into the "provider not found" error?

Indeed, since no provider exists at this point. 😉

To define a provider for CoursesService at the module level, without importing it in the courses.module.ts file, we do the following:

Notice how we've reversed the dependency order—now CoursesModule is imported inside CoursesService instead of the other way around—and we've set up the module-level provider within the service class itself, leveraging the Injectable decorator.

Since CoursesModule no longer imports the service class, the service will be tree-shaken out of the production bundle if it goes unused.

Interestingly, the Injectable decorator isn't limited to defining just module-level providers; it offers a variety of configuration options.

We can use the same useClass, useValue, useExisting, and deps options available when configuring providers at the module or component level.

With providedIn, we're not restricted to module-level providers either. We can even supply services to other modules by exposing them at the root of the module dependency injection tree:

This is likely the syntax you're already familiar with, as it's the most common.

Using this configuration, CoursesService becomes an application-wide singleton, ensuring only one instance exists across the entire app—which is appropriate here since our service is stateless.

That concludes this section; let's now wrap up with a quick rundown of the main points covered throughout this post.

Summary

Clearly, there's much happening behind the scenes within the Angular dependency injection system. 😉

This DI system is incredibly flexible, offering a wealth of powerful configuration options.

At the same time, its most basic usage—simply dropping a class name into a providers array—is straightforward and beginner-friendly.

However, understanding the inner workings of dependency injection pays off significantly when you're architecting your application for maximum modularity.

Thanks to its hierarchical nature and dual injection trees (one for components, one for modules), the DI system lets you precisely control which dependencies are accessible in different parts of your application and which remain hidden.

These capabilities are particularly valuable when building third-party modules intended for widespread use across various apps, as well as when modularizing a large codebase.

Despite this array of advanced features, the default configuration usually just works and remains incredibly easy to use. 😃

I trust you found this post insightful. To dive deeper into other powerful Angular core features, including a thorough exploration of dependency injection, I recommend checking out the Angular Core Deep Dive course.

If you have any questions or feedback, feel free to leave a comment below, and I'll respond promptly.

To stay updated with future posts on Angular, consider subscribing to our newsletter:

For those just starting out with Angular, take a look at the Angular for Beginners Course:

Angular Dependency Injection: Complete Guide — figure 1