Search code examples
angularangular-directive

precedence of local providers in directives and components


I think the local provider for a directive is to provide services for its content children, for example:

//template in a module 
<table>
   <tr>
      <td>{{ item.price | myPipe }}</td>    
   </tr>
</table>

myPipe has a dependency of MyService in its contructor:

So if I define a directive as:

@Directive({
    selector: "[myAttr]", 
    providers: [MyService]
})
export class MyDirective { }

and apply it as:

<table>
   <tr myAttr>
      <td>{{ item.price | myPipe }}</td>    
   </tr>
</table>

then MyService in myPipe's constructor can be resolved.

But if there is a component also define MyService in its local providers and apply it as:

<myComponent>
   <tr myAttr>
      <td>{{ item.price | myPipe }}</td>    
   </tr>
</myComponent>

since both the MyDirective and MyComponent can provider the service for myPipe, so which one will myPipe choose, local provider of MyDirective or MyComponent?


Solution

  • Premise

    Every Component in Angular gets its very own Injector context, which is used to provide Services (or, more generally, Injectables) to itself and its component tree.

    Short answer

    Pipes seem to always use the Injector context of the Component whose template they are in (empirically tested by me, citation needed). So if you have a Component (let's call it parentComponent) with a template that looks like this:

    <myComponent>
       <tr myAttr>
          <td>{{ item.price | myPipe }}</td>    
       </tr>
    </myComponent>
    

    then myPipe will not get the Service instance from either myComponent or myAttr! So the direct answer to your question is neither!

    Instead it will get the Service instance as used (provided) by the declaring Component (i.e. parentComponent).

    Short explanation

    In terms of HTML structure it does indeed seem that myPipe is a child of both myComponent and myAttr. However, in terms of Angular's hierarchy myPipe is a child of parentComponent and so it gets its service provider there. Also, pipes, unlike Components, are not affected by content projection (use of <ng-content>).

    Also...

    It seems that your understanding of how services are provided is not quite right. Here are some corrections of the statements that you made:

    1. "[I]f I define a directive as (...) and apply it (...) then MyService in myPipe's constructor can be resolved" - not correct, as indicated in my answer.
    2. "If there is a component also define MyService in its local providers (...) both the MyDirective and MyComponent can provider the service for myPipe" - not correct either, for the same reason.

    Finally

    Components behave differently from pipes. They form component trees and Injector contexts change accordingly. So if in place of myPipe you had a yetAnotherComponent, it would in fact get MyService as provided by myComponent as opposed to parentComponent.

    Finally finally

    If your directive provides a Service and you apply that Directive to a Component, that Component will get the Service as provided by the Directive! Unless, of course, it has registered a provider itself.