Search code examples
angularangular-directiveangular-componentsangular-dependency-injection

How to pass attribute Directive instance to nested component using Dependency Injection in Angular


I'm trying to use an attribute Directive to pass an element's template reference from a parent component down to a nested component using dependency injection, but the Directive instance in the parent component that gets a hold of the target element is not the same instance that's being injected into the nested component—a second instance of the Directive is being created that doesn't access the target element, and that's the one being injected.

This is the basic structure:

<div>
  <div #myTarget [appendTarget]="myTarget"></div>
  <child>
    <grandchild></grandchild>
  </child>
</div>

I'm trying to pass a reference to #myTarget down to <grandchild> using Directive appendTarget and DI rather than @Inputs (which works fine) so <child> doesn't need to know about it and act as a pass-through.

I've included AppendTargetDirective in the providers array of the parent component:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  providers: [
    AppendTargetDirective
  ]
})
export class AppComponent {
  constructor() { }
}

and injected it into the <grandchild> component:

@Component({
  selector: 'grandchild',
  template: `
<div>
  Hi! Grandchild here...
</div>
  `
})
export class GrandchildComponent  {
  constructor(
    private appendTargetDirective: AppendTargetDirective
  ) {}
}

However, there are two instances of AppendTargetDirective being created, one that grabs the reference to #myTarget and one that doesn't, and GrandchildComponent is getting the latter.

Here's a StackBlitz demonstrating the issue. If you open the console, you'll see that the Directive constructor is called twice (I generate a unique ID for each instance in its constructor), and that the instance that gets a hold of the target element is not the instance that gets injected. I thought that including it in the providers array of the parent component would create a single instance of it that would be shared among the parent component and its descendants, but obviously I'm not thinking correctly.

Any help would be greatly appreciated.


Solution

  • You NEVER shouldn't be adding Angular directives to providers array. Angular will treat them as services which are separated instances of directive classes and not tied to template structure at all.

    Here's how Angular tries to resolve directive dependency in your case.

    It starts from current element and walks up the tree

      3 ^  <div>
            <div #myTarget [appendTarget]="myTarget"></div>
      2 ^     <child>   
      1 ^       <grandchild></grandchild>
              </child>
           </div>
    

    DI for directives will work in case if you put directive on the same branch of HTML tree where your child is placed:

    <div>
      <div #myTarget></div>
      <child [appendTarget]="myTarget">   <--------------------------- move it here
        <grandchild></grandchild>
      </child>
    </div>
    

    Forked Stackblitz