Templates
About
Every Angular Component
has a template that defines the DOM
that the component renders onto the page.
They are usually inside:
*.component.html
file.template
property of a*.component.ts
file.
How they work
Templates are based on HTML
syntax, bu with additional features that are interpreted by Angular.
Angular complies templates into JavaScript in order to build up an internal understanding of your application.
Best Practices
With Template Statements:
Template statements cannot refer to anything in the global namespace like
windows
ordocument
.They cannot call
console.log()
orMath.max()
.Use short expressions, keep application and business logic in the component.
They should have quick execution, since Angular executes a template expression after every change detection cycle.
Consider caching values when their computation requires greater resources.
When
property binding
:Avoid side effects, evaluation of a template expression should have no visible side effects.
Return the proper type, a template expression should result in the type of value that the target property expects.
Keep variables names unique to avoid name collisions or variables shadowing variables in another context.
For
event binding
:Prefer to use
code
for binding to keyboard events.
Supported Values & Operators
Whitespaces
Angular collapse multiple consecutive whitespaces in Templates.
To force Angular to preserve these whitespaces specify preserveWhitespaces: true
in the @Component
decorator.
@Component({
preserveWhitespaces: true
})
You use <ng-content>
element as a placeholder to mark where content should go.
It works similarly to <slot />
the native component, but with some Angular specific functionality.
Don't conditionally include <ng-content>
, for conditional rendering use <ng-template> instead.
<ng-content>
itself is not rendered at the resulting DOM.
@Component({
selector: "custom-card",
template: '<div class="card-shadow"><ng-content></ng-content></div>',
})
export class CustomCard { ... }
<!-- Using the component -->
<custom-card>
<p>This is the projected content</p>
</custom-card>
<!-- The rendered DOM -->
<custom-card>
<div class="card-shadow">
<p>This is the projected content</p>
</div>
</custom-card>
Fallback Content on <ng-content>
<ng-content>
Any added content between <ng-content></ng-content>
tags will be used as fallback content if none was passed.
@Component({
selector: "custom-card",
template: `
<div class="card-shadow">
<ng-content>
<span>Nothing was passed!</span>
</ng-content>
</div>
`,
})
export class CustomCard { ... }
<!-- Using the component -->
<custom-card></custom-card>
<!-- The rendered DOM -->
<custom-card>
<div class="card-shadow">
<span>Nothing was passed!</span>
</div>
</custom-card>
Muliple content placeholder - with select=""
select=""
You may project multiple different elements into different <ng-content>
placeholders based on CSS selectors.
If a component does not include an <ng-content>
placeholder without a select
attribute, any elements that don't match one of the component's placeholders do not render into the DOM.
@Component({
selector: "custom-card",
template: `
<div class="card-shadow">
<ng-content select="card-title"></ng-content>
<div class="card-divider"></div>
<ng-content select="card-body"></ng-content>
<!-- capture anything else except "card-title" and "card-body" -->
<ng-content></ng-content>
</div>
`,
})
<!-- Using the component -->
<custom-card>
<p>This content</p>
<card-title>Hello</card-title>
<p>This middle content</p>
<card-body>Welcome to the example</card-body>
<p>This end content</p>
</custom-card>
<!-- Rendered DOM -->
<custom-card>
<div class="card-shadow">
<card-title>Hello</card-title>
<div class="card-divider"></div>
<card-body>Welcome to the example</card-body>
<p>This content</p>
<p>This middle content</p>
<p>This end content</p>
</div>
</custom-card>
Using select=""
for restricting content
select=""
for restricting contentThe use of select
is not restricted to having multiple <ng-content
>, it can be used to be more restrictive to what can go inside <ng-content>
.
In fact you may also specify multiple selectors
.
@Component({
selector: "form-group",
template: `
<div class="form-group">
<ng-content select="input, textarea"></ng-content>
</div>
`,
})
<!-- Using the component -->
<form-group>
<input type="text" />
</form-group>
<!-- Or -->
<form-group>
<textarea></textarea>
</form-group>
<!-- Anything else passed will not be projected -->
<form-group>
<span>Something</span>
</form-group>
Aliasing content for projection ngProjectAs
ngProjectAs
ngProjectAs
is a special attribute, that allows you to specify a CSS selector on any element.
It will be checked against an <ng-content select="">
with the same CSS selector given.
ngProjectAs
supports only static values and cannot be bound to dynamic expressions.
<!-- Using the component -->
<custom-card>
<h3 ngProjectAs="card-title">Hello</h3>
<card-body>Welcome to the example</card-body>
</custom-card>
This lets you declare a template fragment. (A section of content that you can dynamically or programmatically render)
<ng-template>
contents are NOT automatically rendered to the page.
You will need to programatically render it or use specific directives like ngTemplateOutlet
.
Create a template fragment with:
<p>This is a normal element</p>
<ng-template>
<p>This is a template fragment</p>
</ng-template>
Get a reference to a template fragment by:
Declaring a Template Reference Variables on the
<ng-template #variable>
.By quering for the frament with
viewChild(TemplateRef)
.By injecting the fragment in a directive that is applied directly to an
<ng-template>
element.
In all three cases, the fragment is represented by a TemplateRef
object.
Rendering the Fragments
Once you have the reference to the fragment's TemplateRef
object, you can render the fragment in two ways.
Using NgTemplateOutlet
NgTemplateOutlet
A native directive from Angular that accepts a TemplateRef
and renders the fragment as a sibling to the element with the outlet.
The *ngTemplateOutlet
is used with a <ng-container>
.
<p>This is a normal element</p>
<ng-template #myFragment>
<p>This is a template fragment</p>
</ng-template>
<ng-container *ngTemplateOutlet="myFragment"></ng-container>
This way the content inside <ng-template>
is rendered inside <ng-container>
.
You can additionally pass parameters accepted by the frament, through a context
object corresponding to these paramenters.
And each paramenter is accessed as an attribute prefixed with let-
with a value matching a property name in the context
object.
<ng-template #myFragment let-pizzaTopping="topping">
<p>You selected: {{ pizzaTopping }}</p>
</ng-template>
<ng-container
[ngTemplateOutlet]="myFragment"
[ngTemplateOutletContext]="{topping: 'onion'}"
/>
Using ViewContainerRef
ViewContainerRef
A ViewContainer
is a node in Angular's component tree that can contain content.
Any component or directive can inject ViewContainerRef
to get a reference to a view container corresponding to that component or directive's location in the DOM
.
You then use the createEmbeddedView
Angular method on ViewContainerRef
to dynamically render a template fragment as the next sibling of the component or directive that injected the ViewContainerRef
.
@Component({
template: `
<h2>Component with a fragment</h2>
<ng-template #myFragment>
<p>This is the fragment</p>
</ng-template>
<my-outlet [fragment]="myFragment" />
`,
})
export class ComponentWithFragment { ... }
@Component({
selector: 'my-outlet',
template: `<button (click)="showFragment()">Show</button>`,
})
export class MyOutlet {
private viewContainer = inject(ViewContainerRef);
fragment = input<TemplateRef<unknown>>();
showFragment() {
if (this.fragment) {
this.viewContainer.createEmbeddedView(this.fragment);
}
}
}
You can additionally pass parameters accepted by the frament, through createEmbeddedView
.
And each paramenter is passed as an attribute prefixed with let-
with a value matching a property name in the context
object.
<ng-template #myFragment let-pizzaTopping="topping">
<p>This is the fragment with data: {{ pizzaTopping }}</p>
</ng-template>
this.viewContainer.createEmbeddedView(this.fragment, {topping: 'onion'});
<ng-container>
<ng-container>
It is a special element in Angular that groups multiple elements together or marks a location in a template without rendering a real element in the DOM
.
You may apply directives to <ng-container>
to add behaviors or configuration to a part of your template.
Angular ignores all attribute bindings and event listeners applied to <ng-container>
.
<section>
<ng-container>
<h3>User bio</h3>
<p>Here's some info about the user</p>
</ng-container>
</section>
<!-- Rendered DOM -->
<section>
<h3>User bio</h3>
<p>Here's some info about the user</p>
</section>
Rendering dynamic components
Use Angular's built-in NgComponentOutlet
directive to dynamically render components to the <ng-container>
.
@Component({
template: `
<h2>Your profile</h2>
<ng-container [ngComponentOutlet]="profileComponent()" />
`
})
export class UserProfile {
isAdmin = input(false);
// Where `AdminProfile` and `BasicUserProfile` are other Component Classes
profileComponent = computed(() => this.isAdmin() ? AdminProfile : BasicUserProfile);
}
Rendering template fragments
Use Angular's built-in NgComponentOutlet
directive to dynamically render components to the <ng-container>
.
@Component({
template: `
<h2>Your profile</h2>
<ng-container [ngTemplateOutlet]="profileTemplate()" />
<ng-template #admin>This is the admin profile</ng-template>
<ng-template #basic>This is the basic profile</ng-template>
`
})
export class UserProfile {
isAdmin = input(false);
adminTemplate = viewChild('admin', { read: TemplateRef });
basicTemplate = viewChild('basic', { read: TemplateRef });
profileTemplate = computed(() => this.isAdmin() ? this.adminTemplate() : this.basicTemplate());
}
Last updated