Introduction to Angular Templates¶
What are Angular Templates?¶
Angular templates are HTML files with Angular-specific markup that define how a component's view should be rendered. Templates combine regular HTML with Angular directives, binding expressions, and template syntax to create dynamic user interfaces.
Template Syntax Overview¶
Angular templates extend HTML with:
- Interpolation:
{{ expression }} - Property Binding:
[property]="expression" - Event Binding:
(event)="handler()" - Two-way Binding:
[(ngModel)]="property" - Directives:
*ngIf,*ngFor,ngClass, etc. - Template Reference Variables:
#variable - Pipes:
{{ value | pipe }}
Basic Template Structure¶
Simple Component Template¶
// user-profile.component.ts
@Component({
selector: 'app-user-profile',
template: `
<div class="user-profile">
<h2>{{ user.name }}</h2>
<p>Email: {{ user.email }}</p>
<p>Age: {{ user.age }}</p>
<button (click)="editProfile()">Edit Profile</button>
</div>
`,
styles: [`
.user-profile {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
`]
})
export class UserProfileComponent {
user = {
name: 'John Doe',
email: 'john.doe@example.com',
age: 30
};
editProfile() {
console.log('Edit profile clicked');
}
}
External Template File¶
// user-profile.component.ts
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent {
// Component logic
}
<!-- user-profile.component.html -->
<div class="user-profile">
<div class="user-header">
<img [src]="user.avatarUrl" [alt]="user.name + ' avatar'">
<div class="user-info">
<h2>{{ user.name }}</h2>
<p class="user-title">{{ user.title }}</p>
</div>
</div>
<div class="user-details">
<div class="detail-item">
<label>Email:</label>
<span>{{ user.email }}</span>
</div>
<div class="detail-item">
<label>Phone:</label>
<span>{{ user.phone || 'Not provided' }}</span>
</div>
<div class="detail-item">
<label>Department:</label>
<span>{{ user.department }}</span>
</div>
</div>
<div class="user-actions">
<button
class="btn btn-primary"
(click)="editProfile()"
[disabled]="!canEdit">
Edit Profile
</button>
<button
class="btn btn-secondary"
(click)="viewDetails()">
View Details
</button>
</div>
</div>
Interpolation¶
Basic Interpolation¶
Interpolation displays component data in templates using double curly braces:
export class InterpolationDemoComponent {
title = 'Angular Templates';
userName = 'John Doe';
currentDate = new Date();
count = 42;
isActive = true;
getMessage() {
return `Welcome, ${this.userName}!`;
}
getStatusText() {
return this.isActive ? 'Online' : 'Offline';
}
}
<!-- Basic interpolation -->
<h1>{{ title }}</h1>
<p>Hello, {{ userName }}!</p>
<p>Today is {{ currentDate }}</p>
<p>Count: {{ count }}</p>
<!-- Method calls -->
<p>{{ getMessage() }}</p>
<p>Status: {{ getStatusText() }}</p>
<!-- Expressions -->
<p>Double count: {{ count * 2 }}</p>
<p>Uppercase name: {{ userName.toUpperCase() }}</p>
<p>Is even: {{ count % 2 === 0 }}</p>
<!-- Conditional expressions -->
<p>{{ isActive ? 'User is active' : 'User is inactive' }}</p>
<!-- Safe navigation operator -->
<p>User city: {{ user?.address?.city }}</p>
Advanced Interpolation¶
export class AdvancedInterpolationComponent {
users = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 },
{ name: 'Bob', age: 35 }
];
config = {
showDetails: true,
theme: 'dark',
itemsPerPage: 10
};
getAverageAge() {
return this.users.reduce((sum, user) => sum + user.age, 0) / this.users.length;
}
}
<!-- Array length -->
<p>Total users: {{ users.length }}</p>
<!-- Object properties -->
<p>Theme: {{ config.theme }}</p>
<p>Items per page: {{ config.itemsPerPage }}</p>
<!-- Complex expressions -->
<p>Average age: {{ getAverageAge() | number:'1.1-1' }}</p>
<p>First user: {{ users[0]?.name }}</p>
<p>Last user: {{ users[users.length - 1]?.name }}</p>
<!-- JSON display (useful for debugging) -->
<pre>{{ users | json }}</pre>
Template Expressions¶
Expression Context¶
Template expressions are evaluated against the component instance:
export class ExpressionContextComponent {
title = 'Expression Context Demo';
// Component properties
message = 'Hello from component';
isVisible = true;
items = ['apple', 'banana', 'orange'];
// Component methods
getTitle() {
return this.title.toUpperCase();
}
toggleVisibility() {
this.isVisible = !this.isVisible;
}
}
<!-- Accessing component properties -->
<h2>{{ getTitle() }}</h2>
<p [hidden]="!isVisible">{{ message }}</p>
<!-- Template context in *ngFor -->
<ul>
<li *ngFor="let item of items; let i = index">
{{ i + 1 }}. {{ item }}
</li>
</ul>
<!-- Template reference variables -->
<input #nameInput type="text">
<p>You typed: {{ nameInput.value }}</p>
Expression Guidelines¶
<!-- Good - simple expressions -->
<p>{{ user.name }}</p>
<p>{{ isLoggedIn ? 'Welcome' : 'Please log in' }}</p>
<!-- Avoid - complex logic in templates -->
<!-- Bad: complex business logic -->
<p>{{ users.filter(u => u.age > 18).map(u => u.name).join(', ') }}</p>
<!-- Good: move logic to component -->
<p>{{ adultUserNames }}</p>
// Component method for complex logic
get adultUserNames(): string {
return this.users
.filter(user => user.age > 18)
.map(user => user.name)
.join(', ');
}
Template Statements¶
Template statements respond to events raised by binding targets:
export class TemplateStatementsComponent {
message = '';
items: string[] = [];
counter = 0;
onButtonClick() {
this.counter++;
this.message = `Button clicked ${this.counter} times`;
}
addItem(item: string) {
if (item.trim()) {
this.items.push(item.trim());
}
}
removeItem(index: number) {
this.items.splice(index, 1);
}
onInputChange(event: Event) {
const target = event.target as HTMLInputElement;
this.message = target.value;
}
}
<!-- Event binding statements -->
<button (click)="onButtonClick()">Click me</button>
<p>{{ message }}</p>
<!-- Event with parameters -->
<div *ngFor="let item of items; let i = index">
{{ item }}
<button (click)="removeItem(i)">Remove</button>
</div>
<!-- Input events -->
<input
type="text"
(input)="onInputChange($event)"
(keyup.enter)="addItem($event.target.value); $event.target.value = ''">
<!-- Multiple statements -->
<button (click)="counter++; message = 'Counter: ' + counter">
Increment
</button>
Template Reference Variables¶
Template reference variables provide access to DOM elements and component instances:
Basic Usage¶
<!-- Reference to DOM element -->
<input #nameInput type="text" placeholder="Enter your name">
<button (click)="greet(nameInput.value)">Greet</button>
<!-- Reference to component -->
<app-child-component #childComp></app-child-component>
<button (click)="childComp.doSomething()">Call Child Method</button>
<!-- Reference in loops -->
<div *ngFor="let item of items">
<input #itemInput [value]="item">
<button (click)="updateItem(itemInput.value)">Update</button>
</div>
Advanced Reference Variables¶
<!-- Form references -->
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<input
name="username"
#username="ngModel"
[(ngModel)]="user.username"
required>
<div *ngIf="username.invalid && username.touched">
Username is required
</div>
<button [disabled]="userForm.invalid">Submit</button>
</form>
<!-- Multiple references -->
<input #input type="text" #ngModel="ngModel" [(ngModel)]="value">
<p>Value: {{ input.value }}</p>
<p>Valid: {{ ngModel.valid }}</p>
ViewChild with Template References¶
import { ViewChild, ElementRef } from '@angular/core';
export class ViewChildDemoComponent {
@ViewChild('nameInput') nameInput!: ElementRef<HTMLInputElement>;
@ViewChild('childComp') childComponent!: ChildComponent;
focusInput() {
this.nameInput.nativeElement.focus();
}
callChildMethod() {
this.childComponent.doSomething();
}
}
Template Safety¶
Safe Navigation Operator¶
The safe navigation operator (?.) protects against null and undefined values:
export class SafeNavigationComponent {
user: User | null = null;
config: Config | undefined = undefined;
loadUser() {
// Simulating async data loading
setTimeout(() => {
this.user = {
name: 'John Doe',
profile: {
bio: 'Software Developer',
social: {
twitter: '@johndoe'
}
}
};
}, 1000);
}
}
<!-- Safe navigation prevents errors -->
<div>
<h3>{{ user?.name }}</h3>
<p>{{ user?.profile?.bio }}</p>
<p>Twitter: {{ user?.profile?.social?.twitter }}</p>
<!-- Without safe navigation, this would throw an error if user is null -->
<p>Length of name: {{ user?.name?.length }}</p>
</div>
<!-- Safe navigation with method calls -->
<button (click)="user?.profile?.updateBio?.()">Update Bio</button>
<!-- Safe navigation in property binding -->
<img [src]="user?.profile?.avatar || 'default-avatar.png'">
Non-null Assertion Operator¶
Use the non-null assertion operator (!) when you're certain a value is not null:
export class NonNullAssertionComponent {
user!: User; // Definite assignment assertion
ngOnInit() {
this.user = this.getUserFromService(); // You know this returns a User
}
getUserFromService(): User {
return { name: 'John', email: 'john@example.com' };
}
}
<!-- Non-null assertion -->
<p>{{ user!.name }}</p>
<p>{{ user!.email }}</p>
<!-- Use sparingly and only when you're certain -->
<div [innerHTML]="htmlContent!"></div>
Template Comments¶
<!-- HTML comments are removed from the rendered output -->
<!-- This is an HTML comment -->
<!-- Angular template comment syntax -->
<!-- TODO: Add user validation -->
<input type="text" [(ngModel)]="username">
<!-- Multi-line comments -->
<!--
This is a multi-line comment
that explains complex template logic
-->
<!-- Conditional comments for debugging -->
<!-- DEBUG: User object -->
<!-- <pre>{{ user | json }}</pre> -->
Template Best Practices¶
1. Keep Templates Simple¶
<!-- Good - simple and readable -->
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button (click)="editUser()">Edit</button>
</div>
<!-- Avoid - complex logic in templates -->
<div class="user-card">
<h3>{{ user.firstName + ' ' + user.lastName + (user.title ? ', ' + user.title : '') }}</h3>
<p>{{ user.email || (user.alternateEmail && user.alternateEmail.length > 0 ? user.alternateEmail[0] : 'No email') }}</p>
</div>
2. Use Getters for Computed Properties¶
// Component
get fullName(): string {
return `${this.user.firstName} ${this.user.lastName}${this.user.title ? ', ' + this.user.title : ''}`;
}
get displayEmail(): string {
return this.user.email ||
(this.user.alternateEmail?.length > 0 ? this.user.alternateEmail[0] : 'No email');
}
<!-- Template - clean and readable -->
<div class="user-card">
<h3>{{ fullName }}</h3>
<p>{{ displayEmail }}</p>
</div>
3. Use TrackBy Functions¶
export class TrackByDemoComponent {
users: User[] = [];
trackByUserId(index: number, user: User): number {
return user.id;
}
}
<!-- Optimized ngFor with trackBy -->
<div *ngFor="let user of users; trackBy: trackByUserId">
{{ user.name }}
</div>
4. Organize Complex Templates¶
<!-- Break complex templates into smaller components -->
<div class="dashboard">
<app-header [user]="currentUser"></app-header>
<div class="main-content">
<app-sidebar [navigation]="navItems"></app-sidebar>
<app-content-area [content]="dashboardContent"></app-content-area>
</div>
<app-footer></app-footer>
</div>
5. Use Semantic HTML¶
<!-- Good - semantic HTML -->
<article class="user-profile">
<header>
<h1>{{ user.name }}</h1>
<p>{{ user.title }}</p>
</header>
<section class="contact-info">
<h2>Contact Information</h2>
<address>
<a href="mailto:{{ user.email }}">{{ user.email }}</a>
</address>
</section>
<section class="bio">
<h2>Biography</h2>
<p>{{ user.bio }}</p>
</section>
</article>
Template Debugging¶
Debugging Techniques¶
<!-- JSON pipe for object inspection -->
<pre>{{ user | json }}</pre>
<!-- Conditional debugging -->
<div *ngIf="debugMode">
<h4>Debug Information</h4>
<p>User ID: {{ user.id }}</p>
<p>Is Admin: {{ user.isAdmin }}</p>
<p>Last Login: {{ user.lastLogin | date }}</p>
</div>
<!-- Template reference for debugging -->
<input #debugInput type="text" [(ngModel)]="searchTerm">
<p>Input value: {{ debugInput.value }}</p>
<p>Input type: {{ debugInput.type }}</p>
Angular DevTools¶
Use Angular DevTools to:
- Inspect component properties
- Monitor change detection
- Debug template bindings
- Profile component performance
Common Template Errors¶
1. Undefined Property Access¶
<!-- Error: Cannot read property 'name' of undefined -->
<p>{{ user.name }}</p>
<!-- Fix: Use safe navigation -->
<p>{{ user?.name }}</p>
<!-- Fix: Use conditional display -->
<p *ngIf="user">{{ user.name }}</p>
2. Incorrect Event Binding¶
<!-- Error: Missing parentheses -->
<button click="handleClick()">Click me</button>
<!-- Fix: Proper event binding -->
<button (click)="handleClick()">Click me</button>
3. Property vs Attribute Confusion¶
<!-- Error: Won't work as expected -->
<input disabled="{{ isDisabled }}">
<!-- Fix: Property binding -->
<input [disabled]="isDisabled">
<!-- Fix: Attribute binding (for special cases) -->
<input [attr.aria-disabled]="isDisabled">
Summary¶
Angular templates provide powerful features for creating dynamic UIs:
- Interpolation displays component data
- Template expressions enable dynamic content
- Template statements handle user interactions
- Template reference variables access DOM elements
- Safe navigation prevents runtime errors
- Best practices ensure maintainable code
Key concepts:
- Templates combine HTML with Angular syntax
- Expressions are evaluated against component context
- Use safe navigation to handle null/undefined values
- Keep templates simple and move logic to components
- Use semantic HTML for accessibility and SEO
Next Steps¶
Now that you understand templates, you're ready to explore:
- Data binding in detail (property, event, two-way)
- Angular directives (structural and attribute)
- Pipes for data transformation
- Component communication patterns
- Forms and user input handling
Templates are the foundation of Angular's view layer - master them to create compelling user interfaces!