3

I have been spending more time trying to understand the following blog post Creating Reusable Components with NgTemplateOutlet in Angular

The working code of the post above can be found at stackblitz.

In the UsageExample component the card-or-list-view component is called. The items and mode input parameters are supplied which I understand very well.

Now what I don't understand is how

<ng-container *cardItem="let item">
        <h1>{{item.header}}</h1>
        <p>{{item.content}}</p>
      </ng-container>
      <span *listItem="let item">
        <h1>{{item.header}}</h1>
        <p>{{item.content}}</p>
      </span>

in the UsageExample template replaces

<ng-container *ngSwitchCase="'card'">
    <div *ngFor="let item of items" style="margin: 5px;border: black 1px solid">
      <ng-container *ngTemplateOutlet="cardItemTemplate; context: {$implicit: item}">
      </ng-container>
    </div>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="listItemTemplate; context: {$implicit: item}"></ng-container>
    </li>
  </ul>

in the template of CardOrListViewComponent component. In the CardOrListViewComponent component, two directives are declared

@ContentChild(CardItemDirective, {read: TemplateRef}) cardItemTemplate;
  @ContentChild(ListItemDirective, {read: TemplateRef}) listItemTemplate;

and are used in its templates with *ngTemplateOutlet.

How do these directives got replaced by

<ng-container *cardItem="let item">
        <h1>{{item.header}}</h1>
        <p>{{item.content}}</p>
      </ng-container>
      <span *listItem="let item">
        <h1>{{item.header}}</h1>
        <p>{{item.content}}</p>
      </span>

in the UsageExample component.

How do *cardItem and *listItem connect to the two @ContentChild directives in CardOrListViewComponent component. Their names are not even similar.

halfer
  • 19,824
  • 17
  • 99
  • 186
Eddy Freeman
  • 3,207
  • 6
  • 35
  • 55

1 Answers1

6

First, Angular structural directives have two forms we can use:

1) sugared version

<div *ngIf="foo">bar</div>

2) de-sugared version

<ng-template [ngIf]="foo">
  <div>bar</div>
</ng-template>

Back to your example:

<ng-container *cardItem="let item">
  <h1>{{item.header}}</h1>                            sugar
  <p>{{item.content}}</p>
</ng-container>

       ||
       \/

<ng-template cardItem let-item>
  <ng-container>
    <h1>{{item.header}}</h1>                         de-sugar
    <p>{{item.content}}</p>
  </ng-container>
</ng-template>

Secondly, it's common practice to get reference to something in template through @ViewChild(-dren)|@ContentChild(-dren) decorators.

We can query directives that match template elements. Angular always match de-sugared version of our template. So the directive:

@Directive({
  selector: '[cardItem]'
})
export class CardItemDirective {}

matches the template:

  selector: '[cardItem]' 
               ||
               \/
<ng-template cardItem let-item>

If you don't want all possible cases about what we can query then follow this answer:

Now we know that by using @ContentChild query:

@ContentChild(CardItemDirective) cardItemTemplate;

we get CardItemDirective instance, but we want to get TemplateRef in order to use it in *ngTemplateOutlet.

Here's where read option comes to the rescue:

@ContentChild(CardItemDirective, {read: TemplateRef}) cardItemTemplate;
                                     \/
         please give us TemplateRef instance from the element 
         that matches CardItemDirective selector

Finally, having TemplateRef we can render it with the help of ngTemplateOutlet directive and pass any context we want:

<div *ngFor="let item of items">
  <ng-container *ngTemplateOutlet="cardItemTemplate; context: {$implicit: item}">
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • @yurzui-Thanks for your reply. I still have a little question to clear up things. The directives are declared as CardItemDirective and ListItemDirective in the "CardOrListViewComponent" component and not CardItem and ListItem. How come the names are different but they still match. Doesn't the 'DIRECTIVE' suffix of the name matters? – Eddy Freeman Feb 24 '19 at 06:12
  • It's up to you how to name directives. Angular only uses `selector` to match query for directives. – yurzui Feb 24 '19 at 06:14