20

I'm trying to display two differents ng-content inside two ngFor loops. But as described in this answer, Angular can't project ng-content multiple times.

I tried this solution, but this does not allow me to project multiple times my content, only to display it at different places depending on an ngIf condition.

Here's my actual code :

An example parent component (any component using app-table). That's what I want to do.

<app-table [data]="myData">
  <div lineHeader let-line>
    {{line.name}}
  </div>
  <div lineContent let-element>
    {{element.name}}
  </div>
</app-table>

The child component (app-table). Here I can do everything while the parent component keeps working the same way.

<div *ngFor="let line of data">
  <ng-content select="[lineHeader]"></ng-content>
  <div *ngFor="let element of line">
    <ng-content select="[lineContent]"></ng-content>
  </div>
</div>
Arthur Chaloin
  • 610
  • 1
  • 5
  • 12
  • `I tried this solution` That solution should help you. There is also another solution – yurzui Oct 27 '17 at 13:48
  • Please recheck your code, there are multiple errors. Your property binding (select="[lineHeader]") is wrong. You need to put the square brackets around the property (**[select]="lineHeader"**). – dave0688 Oct 27 '17 at 13:51
  • @dave0688 No no no no ))) – yurzui Oct 27 '17 at 13:52
  • `let-` is only supported on template elements and i don't get how you make for loop `let line of data` and then `let element of line` Where are you going to take `line.name`? – yurzui Oct 27 '17 at 13:55

1 Answers1

56

The best option in your case would be trancluding ng-template by using ngTemplateOutlet like:

<app-table [data]="myData">
  <ng-template #lineHeader let-line>
    <div>
     {{line.name}}
    </div>
  </ng-template>
   <ng-template #lineContent let-element>
    <div>
      {{element.name}}
    </div>
   </ng-template>
</app-table>

app-table.component.ts

@Component({
  selector: 'app-table',
  template: `
    <div *ngFor="let line of data">
      <ng-container *ngTemplateOutlet="lineHeaderTmpl, 
                     context: { $implicit: line }"></ng-container>
      <div *ngFor="let element of line.values">
        <ng-container *ngTemplateOutlet="lineContentTmpl, 
                       context: { $implicit: element }"></ng-container>
      </div>
    </div>
  `
})
export class AppTableComponent  {
  @Input() data: any;

  @ContentChild('lineHeader') lineHeaderTmpl: TemplateRef<any>; 
  @ContentChild('lineContent') lineContentTmpl: TemplateRef<any>; 
}

Stackblitz Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • I heard about `ContentChild` but didn't succeed to use it efficiently. Thanks! – Arthur Chaloin Oct 27 '17 at 14:19
  • THIIIIISSSSSSS! – Chris Knight Feb 24 '18 at 22:34
  • This is awesome. One thing though... is it possible to detect if ng-template exists and if it doesn't exist, then show default? – rain01 Oct 10 '18 at 21:35
  • @rain01 in the above example you could just check to see if the template was found `const noTemplate = !this.lineHeaderTmpl.length;` and show some other content. I've had success using this method in the past: [Gist](https://gist.github.com/benjamincharity/1d81649150a584d9c8673c508afd46d7) – Benjamin Oct 17 '18 at 17:32
  • This is beautiful :') – mrtpain Mar 19 '19 at 02:48
  • Any chance to make this work in a hybrid app (with AngularJS 1)? It's impossible to use ng-template in downgraded components... – Kirill Metrik Jun 09 '19 at 14:50