Richard Hunter
  • Projects
  • Writings
  • Contact

Injectors in Angular Dependency Injection
created: Dec 03 2022
edited: Feb 16 2023

a deep dive into the scope of Component and Directive injectors

dependency injection (DI) is a fundamental feature of Angular. it provides a way for the objects in an application to be wired together. it allows dependencies to be declared as interfaces whose implementations are supplied at runtime, and can even be swapped with different implementations. the benefits are: to decouple objects from their dependencies, to reduce object creation boilerplate, and to facilitate testing units in isolation

Angular's DI system consists of a tree of injectors which largely mirrors the component tree. when a component tries to inject a dependency, it searches in the injector closest to it. if it cannot find it there, it will search upwards through the tree until it finds it, else an error will be thrown (unless the dependency is labelled as optional). by default, the tree has only a single root injector, and this may be sufficient for many applications; however, additional injectors can be configured by both components and directives. a component can configure two injectors through its providers and viewProviders properties. a directive can configure just one injector with its providers property

when working with injectors, it can be confusing to developers what the scope of each injector is. in this article, I'll explore this and provide a clear explanation of when and why you should configure a particular injector for a component or directive

there are four main cases to consider and I'll take each one in turn. for each case, I'll present the following information:

  • template: the template or templates in which component and directives are declared
  • code: relevant code examples
  • injection order: the order in which injectors are searched for the requested dependency
  • logical view: a representation of the component tree as seen from the point of view of the DI system. the syntax used is adapted from the Angular DI docs

Case 1: a single component with providers and viewProviders

let’s start with the simplest case: a component with provider and viewProvider injectors providing services sharing the same DI token

// ChildComponent.ts

  providers: [{provide: MyService, useClass: AlphaService}],
  viewProviders: [{provide: MyService, useClass: BetaService}],

template

<app-child></app-child>

injector order

ChildComponent:viewProviders > ChildComponent:providers

logical view

<app-child @Provide(MyService=AlphaService)>
  <#VIEW @Provide(MyService=BetaService)>
    @Inject(MyService) => BetaService
  </#VIEW>
</app-child>

BetaService is used as it is provided by the injector within scope that is closest to the injection point

Example:

example 1 the component injects the service from the viewProvider injector. if that is commented out, AlphaService from the provider injector is used instead

Case 2: child component selector declared in parent component template

a parent component and a child component where the child is declared within the parent’s template

template

<!— AppComponent template —>
<app-parent></app-parent>

<!— ParentComponent template —>
<app-child></app-child>

injector order

ChildComponent:viewProviders > ChildComponent:providers > ParentComponent:viewProviders > ParentComponent:providers

logical view

<app-parent @Provide(MyService=AlphaService)>
  <#VIEW @Provide(MyService=BetaService)>
    <app-child @Provide(MyService=GammaService)>
      <#VIEW @Provide(MyService=DeltaService)>
        @Inject(MyService) => DeltaService
      </#VIEW>
    </app-child>
  </#VIEW>
</app-parent>

at the injection point, providers and viewProviders of both the child and parent component are all in scope

example

example 2

Case 3: directives on component elements

directives can also add injectors to the injector tree. the @Directive decorator supports the providers property only. all the injectors on a single element are combined into a single injector. the services from directives override those in the component. if there are several directives configuring services, the priority amongst directives is determined by the order in which directives are declared in the module

template

<app-child app-foo></app-child>

injector order

FooDirective:providers > ChildComponent:providers

logical view

<app-child @Provide(MyService=AlphaService) app-foo[@Provide(MyService=BetaService)]>
  <#VIEW>
    @Inject(MyService) => BetaService
  </#VIEW>
</app-child>

the provider injectors for both AppComponent and FooDirective are combined into one injector. where the same DI token appears in both injectors, the service from the directive provider injector takes priority. the viewProvider injector of ChildComponent is invisible to FooDirective

example

example 3

Case 4: content projection

content projection is when a component element is nested within another in the same template. the template for the child component is inserted into that of its parent at the point specified by the <ng-content> element. the child component is still able to access the provider injector of its parent, but the parent’s viewProvider injector is hidden to it

template

<app-parent>
  <app-child></app-child>
</app-parent>

injector order

ChildComponent:viewProviders  > ChildComponent:providers > ParentComponent:providers

logical view

<app-parent @Provide(MyService=AlphaService)>
  <#VIEW @Provide(MyService=BetaService)>
  </#VIEW>
  <#CONTENT>
    <app-child @Provide(MyService=GammaService)>
      <#VIEW @Provide(MyService=DeltaService)>
        @Inject(MyService) => DeltaService
      </#VIEW>
    </app-child>
  </#CONTENT>
</app-parent>

the content is in a different scope from that of the view, thus ChildComponent does not have access to ParentComponent’s viewProvider injector

example

example 4 if you delete providers and viewProviders from ChildComponent, the dependency is resolved from the providers injector of ParentComponent. the viewProviders injector is ignored

Conclusion

a component can configure up to two injectors with the providers and viewProviders properties. a directive can configure one injector with the providers property. these injectors allow different parts of an application to have their own instance of a particular service

a directive declared on a component element can provide services which override the provider injector of the component

viewProviders declare services that are only available for the component's view. directives on the component element and projected content does not have access to these. thus, providers configure publicly available services whilst viewProviders configures services that are private to the component

in the next article on the subject of DI, I am going to explore how resolution modifiers like @Self and @Host change this picture

Featured writings

creating a bar chart using D3

a tutorial demonstrating how to create a vertical bar chart ...

making sense of Angular forms

a deep dive into Angular's two form modules...

Angular Resolution Modifiers and scope

how resolution modifiers like @Host and @Self affect injecto...

Injectors in Angular Dependency Injection

a deep dive into the scope of Component and Directive inject...

commentary on Kara Erickson's talk on Angular Forms

commentary on a talk given by Angular Core developer Kara Er...

Building my first computer game

My first attempt at creating a computer game in Javascript...

Animated page transitions in a Single Page App

In a Single Page App, we can take control of the routing pro...

Problems with Redux and why Streams are better

A discussion on some of the drawbacks of using Redux within ...

Implementing Angular's Hero app using React

Implementing Angular documentation's Hero app using React. ...

Dependency Injection

A discussion on Dependency Injection and a library I have wr...

Ways of making CSS clip-path property work better

Ways of making the clip path better...

Carracci: a UML diagram editing tool

Carracci is a project that I have been working on recently i...
  • Home
  • Projects
  • Writings
  • Contact