2

I know there are a few questions similar to this one but they aren't quite the same. I'm building a nested list and I want to display a custom html content in each grandchild along side common html. When I add the to ListComponent outside of the loop works, but if I pass it inside the loop to the inner child, it doesn't work like the example bellow. The html I pass in the in the code bellow isn't shown. I'm probably trying to solve this the wrong way but I couldn't get it to work any way I tried. Any of you guys know how to make this work?

Thanks!

export class Model {
  title: string;
  children?: Model[] = [];
}

@Component({
  selector: 'list',
  template: `
<ul>
  <li *ngFor="let item of items">
    <list-item [item]="item">
      <div main-content>
        <ng-content select="[main-content]"></ng-content>
      </div>
    </list-item>
    <list [items]="item.children"></list>
  </li>
</ul>
`
})
export class List {
    @Input() items: Model[];
}

@Component({
  selector: 'list-item',
  template: `
<h1>{‌{ item.title }}</h1>
<div class="content">
  <ng-content select="[main-content]"></ng-content>
</div>
`
})
export class ListItem {
  @Input() item: Model;
}

@Component({
  selector: 'app-main',
  template: `
    <list [items]="items">
      <div main-content>
        <h1>Test</h1>
      </div>
    </list>
`
})
export class AppMainComponent {
}

After much testing and going further through the duplicate question that was mentioned and its inner-links, it doesn't quite solve my issue, because the template I'm not trying to duplicate the content as it is in plain. I'm trying to inject into another component with other common html inside it, so if I just iterate through I'll just replicate the the template that I'm passing but I'll lose all other html that is inside.

GustavoAndrade
  • 247
  • 3
  • 15
  • `` doesn't work with `*ngFor`. – Günter Zöchbauer Sep 30 '17 at 12:34
  • Ok, the documentation for this part is pretty weak on angular docs, do you know what is the correct approach to solve this case then? – GustavoAndrade Sep 30 '17 at 12:39
  • See the linked question and the link the answer contains. – Günter Zöchbauer Sep 30 '17 at 13:37
  • @GünterZöchbauer as I edited the answer with more explanation, the question you pointed out doesn't solve my use case. What I'm trying to do may just be impossible, because nothing I tried worked – GustavoAndrade Oct 01 '17 at 15:49
  • You can't project content multiple times. You can only have exactly one `` element without a selector. Additional ones would need to have different `select` attributes. This still doesn't change that you can't project to more than one. Therefore the workarounds mentioned in the linked question are the way to go. – Günter Zöchbauer Oct 01 '17 at 15:58
  • Yeah but those cases doesn't solve this issue. So they aren't a proper answer. – GustavoAndrade Oct 01 '17 at 17:59
  • I still think https://stackoverflow.com/questions/35190188/can-an-ng-content-be-used-inside-of-an-ng-for is the way to go, but perhaps I just don't understand what you actually try to acfimplish. – Günter Zöchbauer Oct 01 '17 at 19:14
  • Well if you are trying to repeat just the plain html transcluded, you are right. But like my issue, if you are trying to repeat the transcluded html with other html elements along side it, it doesn't work. – GustavoAndrade Oct 02 '17 at 17:01

1 Answers1

4

I know it's a little too late but I've recently encountered a similar problem and an answer here would have helped me.

From what I understood you wish to define the template for your list items in the component in which you are using the list. To do that you need to use ng-template in both the child component(the list) and also in the grandchild(the item).

The grandchild should be something like this:

  @Component({
  selector: 'app-item',
  template:`
    <ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: data}">
    </ng-container>
  `
})
export class ItemComponent {
  @Input() public data;
  @ContentChild('itemTemplate') public itemTemplate;
}

And the child:

@Component({
  selector: 'app-list',
  template:`
    <app-item [data]="dataItem" *ngFor="let dataItem of data">
          <ng-template #itemTemplate let-item>
            <ng-container *ngTemplateOutlet="itemTemplate1; context: {$implicit:  item}">
            </ng-container>
          </ng-template>
    </app-item>

  `
})
export class ListComponent {
  @Input() public data;
  @ContentChild('itemTemplate') public itemTemplate1;
}

And the component that uses the list:

<app-list [data]="data">
  <ng-template #itemTemplate let-item>
    <h1>--{{ item?.value}}--</h1>
  </ng-template>
</app-list>

In the item you use an ng-template received as content and pass it the input as context. Inside this template you can define how the item will look inside of your list and though the context you have access to the data of that item. Because you can't just skip a level, you must also expect an ng-template in the list. Inside the list you define the ng-template for the item and inside that ng-template you are just using the ng-template received from above, through an ng-container. And finally at the highest level you can simply define the template for the item.

Theodor
  • 84
  • 3