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 !