Angular's "ng-" elements

AngularJS (1.x) had a lot of attributes that followed the ng- naming convention, but you don't see that prefix much in Angular (2+); ng-repeat is now *ngFor, ng-blur is (blur), etc. However, there are a few core elements named with that prefix. They all share one common trait (they don't get rendered into the resulting DOM) but differ in behaviour and usage. I found it difficult to remember which was which, so wrote this out to help get them straight in my head.

DOM simplification with ng-container

The one you will probably end up writing most frequently yourself is ng-container. This doesn't confer any particular behaviour, and just includes its children in the markup wherever you put it.

<!-- usage -->
<ng-container>
  <p>I am a paragraph.</p>
</ng-container>

<!-- result -->
<p>I am a paragraph.</p>

To see where this could be useful, imagine you want to conditionally include multiple elements without breaking a parent-child relationship between elements (for example in a list or table, or where the relationship is used for a CSS styling rule) or repeating the condition over and over again:

<!-- without ng-container: invalid markup -->
<ul> 
  <li>One</li>
  <li>Two</li>
  <div *ngIf="moreThanTwo">
      <li>Three</li>
      <li>Four</li>
      <li>Five</li>
  </div>
</ul>

<!-- without ng-container: repetition -->
<ul>
  <li>One</li>
  <li>Two</li>
  <li *ngIf="moreThanTwo">Three</li>
  <li *ngIf="moreThanTwo">Four</li>
  <li *ngIf="moreThanTwo">Five</li>
</ul>

<!-- with ng-container -->
<ul> 
  <li>One</li>
  <li>Two</li>
  <ng-container *ngIf="moreThanTwo">
      <li>Three</li>
      <li>Four</li>
      <li>Five</li>
  </ng-container>
</ul>

Content projection with ng-content

If you ever used AngularJS (the 1.x precursor to Angular versions 2 and up), you may have come across "transclusion", the ability to take HTML and inject it into a template at a specified position. That is now referred to as "content projection", but is a similar idea. In this case, the ng-content element is completely replaced by the projected content. For example, given a component with the selector 'my-component':

<!-- template -->
<ng-content></ng-content>

<!-- usage -->
<my-component>
  <p>I am a paragraph.</p>
</my-component>

<!-- result -->
<p>I am a paragraph.</p>

The element also takes a select attribute, which allows you to specify which element gets projected where, especially when you have multiple ng-content elements for different projected content. A more complex example, using classes as selectors:

<!-- template -->
<ng-content select=".first"></ng-content>
<ng-content select=".second"></ng-content>

<!-- usage -->
<my-component>
  <p class="second">I will be last.</p>
  <p class="first">And I will be first.</p>
</my-component>

<!-- result -->
<p class="first">I will be first.</p>
<p class="second">I will be last.</p>

There are three selectors you can use:

  • classes (select=".class");
  • attributes (select="[attribute]"); and
  • elements (select="element").

They can be combined as needed; using all three:

<!-- template -->
<ng-content select="p.foo[aria-label]"></ng-content>

<!-- usage -->
<my-component>
  <p class="foo">Ignore me.</p>
  <p class="foo" aria-label="Projected">Project me.</p>
  <p class="bar" aria-label="Ignored">Ignore me.</p>
</my-component>

<!-- result -->
<p class="foo" aria-label="Projected">Project me.</p>

This can be useful for writing generic components whose markup you'd like the parent component to have some influence over. However, note that you can't do e.g. <ng-content *ngFor="...">, as the content is only ever projected once, and the projected content from the parent can't reference variables in the child.

Note that you can pass down a context if you use a template outlet to render the child content, see ngTemplateOutlet tricks.

<!-- template -->
<div>
  <p class="child">{{ childProperty }}</p>
  <ng-content></ng-content>
</div>

<!-- usage -->
<my-component>
  <p class="parent">{{ childProperty }}</p>
  <p class="parent">{{ parentProperty }}</p>
</my-component>

<!-- result -->
<div>
  <p class="child">I am a property of the child.</p>
  <p class="parent"></p>
  <p class="parent">I am a property of the parent.</p>
</div>

Conditional inclusion with ng-template

The last element you will probably be using very frequently even if you don't ever write it yourself, because it's generated by Angular for structural directives like *ngIf and *ngFor.

ng-template is a bit like ng-container, in that only its children get included in the rendered DOM, but it has conditional behaviour; by default, you'll just see comments in the markup, for example:

<!--bindings={
  "ng-reflect-ng-if": "true"
}-->

<!--bindings={
  "ng-reflect-ng-switch-case": "true"
}-->

<!--bindings={
  "ng-reflect-ng-for-of": "[object Object],[object Object"
}-->

These bindings are the rules that tell Angular what to render and under what circumstances. If the appropriate conditions aren't met, the relevant elements won't be included at all. Structural directives are de-sugared into ng-template elements in two steps:

<!-- original -->
<p *ngIf="condition">Maybe show me?</p>

<!-- step 1 -->
<p template="ngIf condition">Maybe show me?</p>

<!-- step 2 -->
<ng-template [ngIf]="condition">
  <p>Maybe show me?</p>
</ng-template>

As you can see, this turns the syntactic sugar into an ng-template element with the familiar [...]="..." property binding.

If you're using Angular 4.x, you need to use ng-template directly for the new then and else syntax on ngIf, so your alternative elements don't appear in the rendered DOM by default. Below are four ways to show one of two elements depending on a boolean property:

<!-- 2.x version with ngSwitch -->
<div [ngSwitch]="condition">
  <p *ngSwitchCase="true">Condition was true.</p>
  <p *ngSwitchDefault>Condition was false.</p>
</div>

<!-- 2.x version with ngIf twice -->
<div>
  <p *ngIf="condition">Condition was true.</p>
  <p *ngIf="!condition">Condition was false.</p>
</div>

<!-- 4.x version with ngIf; else -->
<div *ngIf="condition; else falseCase">
  <p>Condition was true.</p>
</div>
<ng-template #falseCase>
  <p>Condition was false.</p>
</ng-template>

<!-- 4.x version with ngIf; then; else -->
<div *ngIf="condition; then trueCase; else falseCase"></div>
<ng-template #trueCase>
 <p>Condition was true.</p>
</ng-template>
<ng-template #falseCase>
  <p>Condition was false.</p>
</ng-template>

<!-- result (condition=true) -->
<div>
  <p>Condition was true.</p>
</div>

Comments !