How to render nested ng-template

Vladimir picture Vladimir · Nov 20, 2017 · Viewed 8.9k times · Source

I have such nested ng-template structure.

@Component({
  selector: 'my-app',
  template: `
    <list>
      <ng-template *ngFor="let item of Items" #ListItem>
        <ng-template #ListItemLine>{{ item[0] }}</ng-template>
        <ng-template #ListItemLine>{{ item[1] }}</ng-template>
        <ng-template #ListItemLine>{{ item[2] }}</ng-template>
        I can see this line, but can't get above templates
      </ng-template>
    </list>
  `,
})
export class App {
  Items: Array<Array<string>> = [
    [ "1-1", "1-2", "1-3", ],
    [ "2-1", "2-2", "2-3", ],
    [ "3-1", "3-2", "3-3", ],
    [ "4-1", "4-2", "4-3", ],
    [ "5-1", "5-2", "5-3", ],
  ]
}

How can I render children ng-component in my component:

@Component({
  selector: 'list',
  template: `
    <ul>
      <li *ngFor="let item of Items">
        <ng-container *ngTemplateOutlet="item"></ng-container>
      </li>
    </ul>
  `,
})
export class ListComponent {

  @ContentChildren('ListItem') public Items;

}

Plunkr https://plnkr.co/edit/Hi1ZqPAAYyIbclUuzRrl?p=preview

Thank you in advance.

Update

At the end I want to wrap Angular Material components into my components, so if I would find a btter UI than Material, I shouldn't have to change all accurances of Material components in my program, I would just need to change implementation of my wrapper UI components.

For exmple let's try to wrap mat-list component. I need to make a wrapper for mat-list container, let's call it my-list:

@Component({
  selector: 'my-list',
  template: `
    <mat-list>
      <ng-content></ng-content>
    </mat-list>
  `,
})

and a wrapper for mat-list-item:

@Component({
  selector: 'my-list-item',
  template: `
    <mat-list-item>
      <ng-content></ng-content>
    </mat-list>
  `,
})

The HTML render result would be:

  • my-list
    • mat-list
      • my-list-item
        • mat-list-item
      • ...

Each Material component surrounded by my wrapper, so directives and styles which come with Material will not work.

Answer

AlesD picture AlesD · Nov 21, 2017

Here is how I would solve this based on the information you provided. As you already suggested I have made two components list and list-item. The list component renders the list-item components based on an input array and the content of the list component must contain a template that defines the list item template. Sample code:

    @Component({
      selector: 'list',
      template: `
        <ul>
          <list-item *ngFor="let item of items" [data]="item" [itemTemplate]="itemTemplate"></list-item>
          <ng-content></ng-content>
        </ul>
      `,
    })
    export class ListComponent {
      @ContentChild(TemplateRef)
      public itemTemplate: TemplateRef;
      @Input()
      public items: any[];

    }

Then the list-item component just renders the list item template provided in the a container element. Sample code:

@Component({
  selector: 'list-item',
  template: `
      <li>
        <ng-container *ngTemplateOutlet="itemTemplate; itemContext"></ng-container>
      </li>
  `,
})
export class ListItemComponent {
  @Input()
  public data: any;
  @Input()
  public itemTemplate: Template;
  public get itemContext(): any {
    return { $implicit: data };
  }
}

You can then use it in different ways. I made an example just a simple list and another where the item template is again a list so you get a list of lists simmilar to a tree. Sample code:

@Component({
  selector: 'my-app',
  template: `
    <list [items]="Items">
      <ng-template let-items>{{ items | json }}</ng-template>
    </list>
    <list [items]="Items">
      <ng-template let-items>
        <list [items]="items">
          <ng-template let-items>{{ items | json }}</ng-template>
        </list>
      </ng-template>
    </list>
  `,
})
export class App {
  Items: Array<Array<string>> = [
    [ "1-1", "1-2", "1-3", ],
    [ "2-1", "2-2", "2-3", ],
    [ "3-1", "3-2", "3-3", ],
    [ "4-1", "4-2", "4-3", ],
    [ "5-1", "5-2", "5-3", ],
  ]
}

I made also a plunker sample with the code where you can run it. Plunker Sample