← Back to list

Angular Dependency Injection : Services, Tokens, and Best Practices

Services

Jaouadirabeb · 2025-11-10 19:52 · 0 claps · 5.1 min read
#angular #dependency-injection #angular-services
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Angular Dependency Injection : Services, Tokens, and Best Practices

Services

Services allow you to share data and functionality across different parts of an application.

Creating an Injectable service

Dependency Injection

Dependency Injection (DI) allows you to request instances of services from Angular instead of creating them manually.

Injecting services using Constructor / inject()

The inject method takes a token, which corresponds to the name of the service class. Angular dependency injection hierarchy

Injector Hierarchy

Angular uses multiple injectors to manage where and how services can be registered.

To make a class or value injectable, Angular must be aware of it. This is done by registering it with an injector, allowing Angular to provide it whenever it is requested.

Angular organizes injectors in a hierarchy to manage how dependencies (services, tokens, etc.) are shared across the application. When a dependency is requested, Angular first looks in the nearest injector and then moves up the hierarchy until it finds the requested provider. If no matching provider is found, Angular throws an error.

PlatformInjector

  • The top-level injector in Angular.
  • Created when the Angular platform is initialized (before any app modules).

Root Injector

  • Created when the root module (AppModule) is bootstrapped.
  • It’s the main injector for your app.
  • Services here are singletons, one instance shared across the whole app.

  • Services listed in providers are registered with the root injector.
  • They behave like services provided with providedIn: root.

Module Injector

  • Created when Angular loads a feature module (like UserModule, AdminModule, etc.).
  • Used when the module has its own providers.

ElementInjector

  • Created for each component or directive in the component tree.
  • All child components and directives in the subtree of that component have access to the services provided here, unless a child overrides it with its own provider.
  • It stores providers defined directly in that component :

When a dependency is requested inside a component, Angular first checks that component’s ElementInjector.

  • When a service is provided in a component’s providers array, Angular creates a new instance of that service for each component instance.
  • This is different from root-level services, which are singletons shared across the entire app.

NullInjector

  • The final stop in the injector hierarchy.
  • If Angular reaches the NullInjector and still can’t find the requested dependency, it throws an error:

Example lookup flow

Suppose a component requests a service :

  1. Angular checks the component’s ElementInjector (component-level).
  2. If not found, it goes up to parent components.
  3. Then to any ModuleInjector (module-level), Only relevant if the component belongs to a module that provides the service.
  4. Then to the Root Injector.
  5. Finally, if not found, the NullInjector throws an error.

Issue with services in bootstrapApplication()

  • When you declare a service in the providers array of bootstrapApplication(), it’s registered in the root injector.
  • Root-level services are eagerly instantiated (or at least included in the initial bundle).
  • This means even if a component or feature never uses the service, the code for that service is still loaded.

Inject services into other services

  • You can inject one service into another because both are managed by the root injector.
  • However, you cannot inject a service provided via an ElementInjector into another service.
  • This is because :
  • Services are not part of the component tree or DOM, so they don’t have access to component-level injectors.
  • The ElementInjector hierarchy only applies to components and directives, not standalone services.

Exploring the Injection Tree

You can see Angular’s injection hierarchy in action using Angular DevTools. The Injectables tab (also called the Dependency Injection Tree) allows you to browse through the injector hierarchy and see which services are registered where, whether at the component (ElementInjector), module, or root level.

This is a great way to debug or understand how Angular resolves dependencies and which injector provides a given service.

Custom DI Token

By default, the name of token equal the name of class service.

Inject Values / Constants

You can provide non-class values using InjectionToken :

Angular uses this token to inject the array anywhere it’s needed.

  • Defines a type → TaskStatusOptions
  • Creates a token → TASK_STATUS_OPTIONS
  • Provides a constant array → TaskStatusOptions
  • Exposes a provider object → TaskStatusOptionsProvider

-> This pattern makes it type-safe, reusable, and injectable anywhere in the Angular app.

Best Practices

Use the spread operator to maintain immutability

Here you’re using the spread operator (…) to create a new copy of the oldTasks array, then you add newTask to it.

Why creating a copy is beneficial Immutability : Avoids mutating the existing state

If you did this:

you would modify the original array in place, this is a mutation. That can cause problems because many frameworks (like Angular Signals, React state, or NgRx) detect changes by comparing references.

By creating a new array, you ensure: • A new reference is created. • The framework detects that the state actually changed. • UI updates correctly.

Always create new objects instead of modifying existing ones

The map() function creates a new array, and { …task, status: newStatus } creates a new task object, ensuring immutability throughout the update process.

Prefer read-only signals for safety and separation

Prefer using read-only signals instead of writable signals, this ensures a clear separation between the layer that manages your data and the one that consumes it. Make the tasks signal private and expose a read-only signal instead, this guarantees that the task list cannot be accidentally modified from other components.


메타데이터
post_id
8499fe09aeff
slug
angular-dependency-injection-services-tokens-and-best-practices-8499fe09aeff
url
https://medium.com/@jaouadirabeb/angular-dependency-injection-services-tokens-and-best-practices-8499fe09aeff
canonical_url
https://medium.com/@jaouadirabeb/angular-dependency-injection-services-tokens-and-best-practices-8499fe09aeff
author_url
https://medium.com/@jaouadirabeb
status
ok
fetched_at
2026-06-23 03:48:11