Search code examples
angularangular-content-projection

Angular Content projection with nested children


StackBlitz Eample is here

I have a component where I would like the user to define there own template and then I want to pass that template to some nested children. I'm having issues getting an instance of templates in the parent component.

I have my directive here

@Directive({
  selector: '[customTemplate]',
})
export class CustomTemplate {
  public field!: string;

  constructor(public el: ElementRef) {}
}

I want to put this inside of a parent and get all instances of these directives.

<div customTemplate field="test1">
  <ng-template myTemplate>
    <P>Some text here T1</P>
  </ng-template>
</div>
<div customTemplate field="test2">
  <ng-template myTemplate>
    <P>Some text here T2</P>
  </ng-template>
</div>

<Child1>
  <Child2>I'm going to render the ng-templates of myTemplate here</Child2>
</Child1>

In my Parent Component I have this

    @Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements AfterViewInit {
  @ContentChildren(CustomTemplate)
  public templates?: QueryList<CustomTemplate>;

  ngAfterViewInit(): void {
    console.log(this.templates);
    //here how do I get the instances of CustomTemplate
    
  }
}

The QueryList is empty enter image description here


Solution

  • I believe that content children might not be the right call. Using ViewChildren results in them being discovered. However, a few modifications need to be made.

    Your selector in your directive is set as [customTemplate]. This is fine, however the selectors are case sensitive. Thus in your HTML when you use it as CustomTemplate it won't be discovered. Thus change your HTML to be either customTemplate or update your selector to have a capital C.

    Secondly, in order to do anything of value with said ViewChildren, as is mentioned here (@ViewChildren Querylist Array is empty when children are rendered with *ngFor) you need to subscribe to any changes that occur in your AfterViewInit

    ex:

    this.templates.changes.subscribe((x) => console.log("From Change Sub: ", x));
    

    Lastly, instead of trying to find the children using a string literal, pass in the actual class name instead as so:

    @ViewChildren(CustomTemplate) instead of @ViewChildren('CustomTemplate') or @ViewChildren('customTemplate')

    The latter two do not work, the first one does. Also with the first one if you ever do decide to change the name of the class it will propagate throughout the app, and anywhere it did not the typechecker will find it.

    Updated stackblitz https://stackblitz.com/edit/angular-z5a8ae?file=src/app/app.component.html