Thursday, 7 June 2018

Angular – Structural directive - Group sibling elements with ng-container

There's often a root element that can and should host the structural directive. The list element (<li>) is a typical host element of an NgFor repeater.
src/app/app.component.html (ngfor-li)
<li *ngFor="let hero of heroes">{{hero.name}}</li>
When there isn't a host element, you can usually wrap the content in a native HTML container element, such as a <div>, and attach the directive to that wrapper.
src/app/app.component.html (ngif)
<div *ngIf="hero" class="name">{{hero.name}}</div>
Introducing another container element—typically a <span> or <div>—to group the elements under a single root is usually harmless. Usually ... but not always.
The grouping element may break the template appearance because CSS styles neither expect nor accommodate the new layout. For example, suppose you have the following paragraph layout.
src/app/app.component.html (ngif-span)
<p>
  I turned the corner
  <span *ngIf="hero">
    and saw {{hero.name}}. I waved
  </span>
  and continued on my way.
</p>
You also have a CSS style rule that happens to apply to a <span> within a <p>aragraph.
src/app/app.component.css (p-span)
p span { color: red; font-size: 70%; }
The constructed paragraph renders strangely.


The p span style, intended for use elsewhere, was inadvertently applied here.
Another problem: some HTML elements require all immediate children to be of a specific type. For example, the <select> element requires <option> children. You can't wrap the options in a conditional <div> or a <span>.
When you try this,
src/app/app.component.html (select-span)
<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <span *ngFor="let h of heroes">
    <span *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </span>
  </span>
</select>
the drop down is empty.


The browser won't display an <option> within a <span>.

<ng-container> to the rescue
The Angular <ng-container> is a grouping element that doesn't interfere with styles or layout because Angular doesn't put it in the DOM.
Here's the conditional paragraph again, this time using <ng-container>.
src/app/app.component.html (ngif-ngcontainer)
<p>
  I turned the corner
  <ng-container *ngIf="hero">
    and saw {{hero.name}}. I waved
  </ng-container>
  and continued on my way.
</p>
It renders properly.


Now conditionally exclude a select <option> with <ng-container>.
src/app/app.component.html (select-ngcontainer)
<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <ng-container *ngFor="let h of heroes">
    <ng-container *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </ng-container>
  </ng-container>
</select>
The drop down works properly.










No comments:

Post a Comment