7

So I'm trying to display child components at specific locations in my Component.

I have the following:

@Component({
  selector: 'app-item-list',
  template: `
<p *ngFor="let item of items">
  {{item.order}}
  <!-- display content of child component here -->
</p>`
})
export class ItemListComponent implements OnInit, AfterContentInit {

  //Get all child components of the type ItemComponent and store them.
  @ContentChildren(ItemComponent, {descendants: true}) 
  items?: QueryList<ItemComponent>;
  constructor() { }

  ngOnInit() {
  }
    
  ngAfterContentInit(): void {
      
  }

}

@Component({
  selector: 'app-item',
  template: `
<p>
  item works!
</p>
<ng-content></ng-content>
`
})
export class ItemComponent implements OnInit {

  @Input() order?: string;
  constructor() { }

  ngOnInit() {
  }

}

I tried to display it using <ng-content select="item"> but that didn't work.

With the following:

<app-item-list>
  <app-item order="2">
    test2
  </app-item>
  <app-item order="1">
    test1
  </app-item>
</app-item-list>

the expected output is

2
item works!
test2
1
item works!
test1

https://stackblitz.com/edit/angular-ivy-2aibgt

Peter
  • 37,042
  • 39
  • 142
  • 198

4 Answers4

8

I can suggest you a solution that is used in Angular material library.

  • Wrap content of app-item component in <ng-template> tag

item.component.html

<ng-template>  <----------------------------- wrapper
  <p>
    item works!
  </p>
  <ng-content></ng-content>
</ng-template>
  • then we can grab a reference to that TemplateRef

item.component.ts

@ViewChild(TemplateRef, {static: true}) template: TemplateRef<any>;
  • finally we can use it in a loop of app-item-list component

item-list.component.html

<p *ngFor="let item of items">
  {{item.order}}
  <ng-template [ngTemplateOutlet]="item.template"></ng-template>
</p>

Forked Stackblitz

yurzui
  • 205,937
  • 32
  • 433
  • 399
0

There is really no way to achieve this using ng-content (as far as I know). ng-content is for static content projection only.

Have a look at the use case here - https://github.com/angular/angular/issues/8563 - paragraph 2 confirms the fact that you have to use a query with viewref to do what you want to do i.e. the solution that @yurzui provided.

You can always use the selector to statically select which part of the content you want to project, but I suspect this is not what you're looking for:

<ng-content select="[order=2]">
</ng-content>
<ng-content select="[order=1]">
</ng-content>
Rei Mavronicolas
  • 1,387
  • 9
  • 12
0

You may missed add select item reference variable in item-list.component.html

item.component.html

<p>item works</p>
<ng-content select="[body]">
</ng-content>

item-list.component.html

<p *ngFor="let item of items">
  <app-item >
  <div body>
      {{item.order}}
  </div>
  </app-item>
  test2
</p>

Ref: Stackblitz example

-1

Take a look at this. Ealier I thought there was not way to achieve your output but I finally figured it out.

What I did was eliminate the @ContentChildren approach and instead replaced it with an items Input passed from the app component. I iterated through the items, displaying the order and the corresponding app-item component.

Dev Yego
  • 539
  • 2
  • 12
  • Please explain in your answer what you did. It will help the questioner as well the community. – Roy Jul 20 '20 at 08:48
  • Sadly this is not a solution that matches what I'm after i want to use angular markup to define the content. – Peter Jul 20 '20 at 09:34