Search code examples
javascriptangularangular-directive

Accessing ViewChildren/ContentChildren in a structural Directive


I would like to create a parent directive which shows or hides children based on the values of the children. To do this, i've taken the approach of a parent structural directive, and a child directive with values. For simplicity without the values:

<div *appParent>
  <div appChild>1</div>
  <div appChild>2</div>
  <div appChild>3</div>
  <div appChild>4</div>
</div>

To access the children, I use the following line in the parent directive:

 @ContentChildren(AppChildDirective, { read: AppChildDirective, descendents: true }) children: QueryList<AppChildDirective>;

This query list is always empty. However, when I change it to a non-structural, it works fine. Stackblitz demo here

I assume this is due to the fact the structural directive creates a parent ng-template, which @ContentChildren then looks inside to find the original component, meaning that the query actually goes nowhere.

What approach can I take to access the children of the original component and not the template? Or do I need to take another approach to handle my requirements?


Solution

  • ContentChildren seem to not work on structural directives. However, this can be achived by injecting the parent directive in the child and then registering the child in the parent by calling a function.

    @Directive({
      selector: '[appChild]'
    })
    export class ChildDirective {
      constructor(parent: ParentDirective) { 
        parent.registerChild(this);
      }
    }
    
    @Directive({
      selector: '[appParent]'
    })
    export class ParentDirective {
      registerChild(child: ChildDirective) { /*...*/ }
    }
    

    Side notes

    If you also want to be able to use the child directive without the parent directive, change the child's constructor like this to make the parent optional:

    constructor(@Optional() parent: ParentDirective) { 
      parent?.registerChild(this);
    }
    

    You can also use this approach recursively by injecting a directive in its own constructor. If you do so, also add @SkipSelf() in the constructor to really get the parent:

    @Directive({
      selector: '[appRecursive]'
    })
    export class RecursiveDirective {
      constructor(@Optional() @SkipSelf() parent: RecursiveDirective) { 
        parent?.registerChild(this);
      }
    
      registerChild(child: RecursiveDirective) { /*...*/ }
    }