Devot Logo
Devot Logo
Arrow leftBack to blogs

Dependency Injection in Angular: A Complete Guide

Hrvoje D.5 min readJul 1, 2025Technology
Hrvoje D.5 min read
Contents:
What is dependency injection?
Dependency injection with standalone components
Functional injection: A modern approach
Advanced DI Concepts
Best practices and common pitfalls
Conclusion

Over the past couple of years, Angular, one of the most used frontend frameworks, has undergone significant changes that have reshaped how development is done.

While improvements like typed reactive forms or hydration for server-side rendering have captured attention, one of the most influential changes came early on: the addition of standalone components and, thus, orientation towards functional programming. This change, in turn, brought along important updates to Angular’s Dependency Injection (DI) system, making it more flexible and easier to use.

What is dependency injection?

Dependency injection is a powerful design pattern that underpins Angular’s architecture. It enables component modularity and the separation of concerns by delivering the needed parts of the application to the consumers (components or services) that request them. At its core, DI consists of several key concepts:

  • Injector

  • Injectable

  • Provider

  • Consumer

Injector

The injector is the backbone of the DI system. It maintains a registry of providers and is responsible for creating and delivering instances of classes when requested. When a component or service declares a dependency, Angular’s injector checks its registry: if an instance already exists, it returns that instance; otherwise, it creates a new one.

Angular uses a hierarchical injector system, meaning that while there’s a root-level injector for the entire application, child injectors can also be created for specific modules or components. This hierarchy not only helps manage dependencies but also allows for more granular control over the scope of services.

Injectable

The @Injectable decorator marks a class as available for DI. When a class is decorated with @Injectable, it tells Angular that it can inject instances of this class wherever needed. In most cases, developers also specify the provider’s scope with the providedIn property:

In this example, MyService is registered in the root injector, meaning a single instance is shared across the application. However, services can also be provided at a more granular level if their scope needs to be limited.

Provider

A provider tells Angular’s injector how to create a dependency. Providers can be defined in several ways:

  • Class Provider: The most common approach, where a developer simply provides the class.

  • Value Provider: When a developer wants to inject a constant value.

  • Factory Provider: When the dependency creation is more complex and requires a custom factory function.

  • Alias Provider: When a dependency token needs to be created to resolve to another.

  • Multi-Provider: When multiple values need to be associated with a single token.

By configuring providers correctly, the developer controls how and when the services are instantiated. For instance, providing a service at the component level will result in a new instance every time that component is created.

Consumer

Consumers are the components or services that use the injected values. They declare their dependencies in their constructors, and Angular’s DI system takes care of providing them. For example:

Here, MyComponent acts as a consumer by requesting an instance of MyService in its constructor. The DI system ensures that the correct instance is provided, whether it’s the singleton from the root or a new one if registered at a lower level.

Dependency injection with standalone components

Angular’s introduction of standalone components further simplifies the DI landscape. Standalone components remove the need for Angular modules (NgModules) in many cases, allowing a developer to declare providers directly within the component metadata.

For example:

This approach streamlines development by keeping component declarations and their dependencies in one place, leading to better organization and easier testing.

Functional injection: A modern approach

Starting with Angular 14, developers can now leverage the inject function to retrieve dependencies, which allows for a more functional and concise style compared to the traditional constructor-based injection.

The new functional approach simplifies the process by allowing to directly inject dependencies into class fields. Here is an example with a service and with a constant:

Benefits of using inject

  • Cleaner syntax: By removing the need for constructor parameters, classes become less cluttered and more focused on their core logic.

  • Immediate availability: The dependency is available as soon as the field is declared, which can be advantageous in certain scenarios, such as lazy-loaded values or initializing class properties.

  • Flexibility: This approach aligns well with standalone components and functional programming patterns, providing a consistent way to manage dependencies across different parts of the application.

Things to keep in mind

  • Context-specific: The inject function is designed to be used in contexts where Angular’s DI system is active (e.g., during component or service initialization). Calling it outside of these contexts may result in errors.

  • Testing considerations: When writing unit tests, a developer needs to ensure that the DI context is correctly set up for classes using inject. This is generally similar to testing components with constructor injection, but it's something to be aware of.

Choosing between approaches

Both methods ultimately achieve the same goal—providing the component or service with the dependencies it needs. The choice between constructor injection and functional injection depends on the project's style, readability preferences, and specific use cases.

If one prefers a more declarative and concise style, especially in standalone components or utility functions, the inject function can be a great addition to one's Angular toolkit.

Advanced DI Concepts

Hierarchical Injectors

Angular’s DI system is hierarchical. This means that providers declared in a component are only available to that component and its children, while providers declared in the root are available globally.

This hierarchy is particularly useful when one needs to override a dependency in a specific part of the application without affecting the entire app.

Multi-Providers

Sometimes, a developer needs multiple values to be associated with a single token. Angular’s multi-provider system allows this by letting an array of values be provided. Multi-providers are often used for things like logging or configuring multiple strategies for a given operation.

Injection Tokens

When injecting values that aren’t classes (like configuration objects or strings), Angular provides the InjectionToken class. This ensures that the DI system can safely reference these values without naming collisions.

Token can be provided in a module or component:

Best practices and common pitfalls

Choose the right scope

Decide whether a service should be provided at the root or component level. Global services reduce boilerplate but may lead to unwanted shared state, while component-level providers offer isolation.

Avoid circular dependencies

Ensure that the services and components do not depend on each other in a way that creates a cycle. Circular dependencies can lead to runtime errors and make debugging difficult.

Use Injection Tokens wisely

For non-class values, always use InjectionToken to prevent naming conflicts and to improve clarity.

Leverage Angular’s hierarchical DI

Understand how Angular’s injector hierarchy works to avoid unexpected behaviors, especially when overriding providers in nested components.

Conclusion

Angular’s dependency injection system is a powerful tool that helps manage complexity, improve testability, and promote a clean separation of concerns within your application. Whether you’re using non-standalone components or the newer standalone components, mastering DI will allow you to build more scalable, maintainable, and modular applications.

By understanding the roles of injectors, injectables, providers, and consumers and by applying best practices, you can leverage Angular’s DI to its fullest potential.

Spread the word:
Keep readingSimilar blogs for further insights
Introduction to Firebase: Empowering Your App Development
Technology
Vladimir Š.4 min readJun 24, 2025
Introduction to Firebase: Empowering Your App DevelopmentLearn how to integrate Firebase Authentication with your Spring Boot application. Follow this step-by-step tutorial to set up secure login, role-based access control, and Firebase Admin SDK integration.
How Figma Dev Mode Improves Collaboration Between Designers and Developers
Technology
Luka C.6 min readJun 18, 2025
How Figma Dev Mode Improves Collaboration Between Designers and DevelopersFigma Dev Mode helps frontend teams build faster and more consistently by bridging design and code. Learn how early collaboration and reusable components improve workflow quality.
Next.js vs React: Which One Is Right for Your Next Web Project?
Technology
Mario F.4 min readJun 11, 2025
Next.js vs React: Which One Is Right for Your Next Web Project?Next.js vs React isn’t just a framework debate, it’s a decision that shapes performance, SEO, and development speed. This guide breaks down their key differences to help you choose the right tool for your next project.