Skip to main content

Angular Components & Templates

What is a Component?

Components are the fundamental building blocks of Angular applications. Each component controls a portion of the screen called a view through its associated template.

Component Anatomy

import { Component } from '@angular/core';

@Component({
selector: 'app-user-profile', // How to use in HTML
templateUrl: './user-profile.component.html', // Template file
styleUrls: ['./user-profile.component.css'], // Component styles
standalone: true, // Standalone component (Angular 15+)
})
export class UserProfileComponent {
// Component logic goes here
name = 'John Doe';
age = 30;

greet() {
console.log(`Hello, ${this.name}!`);
}
}

Component Decorator Properties

  • selector: CSS selector that identifies the component in templates
  • templateUrl / template: External file or inline HTML
  • styleUrls / styles: External files or inline CSS
  • standalone: Makes component self-contained (no NgModule needed)
  • imports: Dependencies for standalone components
  • providers: Services available to this component

Template Syntax

1. Interpolation {{ }}

Display component properties in the template:

@Component({
selector: 'app-greeting',
standalone: true,
template: `
<h1>{{ title }}</h1>
<p>Welcome, {{ user.name }}!</p>
<p>You have {{ messageCount }} new messages</p>
<p>{{ 2 + 2 }}</p>
<p>{{ getFormattedDate() }}</p>
`,
})
export class GreetingComponent {
title = 'Dashboard';
user = { name: 'Alice', email: 'alice@example.com' };
messageCount = 5;

getFormattedDate() {
return new Date().toLocaleDateString();
}
}

2. Property Binding [property]

Bind component properties to element properties:

@Component({
selector: 'app-image-display',
standalone: true,
template: `
<!-- Bind to src property -->
<img [src]="imageUrl" [alt]="imageDescription" />

<!-- Bind to disabled property -->
<button [disabled]="isLoading">Submit</button>

<!-- Bind to class -->
<div [class.active]="isActive">Content</div>

<!-- Bind to style -->
<p [style.color]="textColor">Colored text</p>
`,
})
export class ImageDisplayComponent {
imageUrl = 'assets/logo.png';
imageDescription = 'Company Logo';
isLoading = false;
isActive = true;
textColor = '#3b82f6';
}

3. Event Binding (event)

Respond to user events:

@Component({
selector: 'app-counter',
standalone: true,
template: `
<div>
<p>Count: {{ count }}</p>
<button (click)="increment()">+1</button>
<button (click)="decrement()">-1</button>
<button (click)="reset()">Reset</button>

<input (input)="onInput($event)" [value]="searchText" />
<p>You typed: {{ searchText }}</p>
</div>
`,
})
export class CounterComponent {
count = 0;
searchText = '';

increment() {
this.count++;
}

decrement() {
this.count--;
}

reset() {
this.count = 0;
}

onInput(event: Event) {
this.searchText = (event.target as HTMLInputElement).value;
}
}

4. Two-Way Binding [(ngModel)]

Combine property and event binding:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
selector: 'app-user-form',
standalone: true,
imports: [FormsModule], // Required for ngModel
template: `
<div>
<input [(ngModel)]="username" placeholder="Enter username" />
<p>Hello, {{ username }}!</p>

<input type="checkbox" [(ngModel)]="agreed" />
<label>I agree to terms</label>
<p>Agreed: {{ agreed }}</p>
</div>
`,
})
export class UserFormComponent {
username = '';
agreed = false;
}

Template Reference Variables

Access elements or directives in the template:

@Component({
selector: 'app-phone-input',
standalone: true,
template: `
<!-- #phone is a template reference variable -->
<input #phoneInput type="tel" placeholder="Phone number" />
<button (click)="callPhone(phoneInput.value)">Call</button>

<!-- Access element properties -->
<p>Current value: {{ phoneInput.value }}</p>
`,
})
export class PhoneInputComponent {
callPhone(number: string) {
console.log('Calling:', number);
}
}

Structural Directives

*ngIf - Conditional Rendering

@Component({
selector: 'app-user-status',
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="isLoggedIn">
<p>Welcome back, {{ username }}!</p>
</div>

<div *ngIf="!isLoggedIn">
<p>Please log in</p>
</div>

<!-- With else -->
<div *ngIf="hasData; else loading">
<p>Data loaded successfully</p>
</div>
<ng-template #loading>
<p>Loading...</p>
</ng-template>

<!-- With then and else -->
<div *ngIf="user$ | async as user; else noUser">
<p>{{ user.name }}</p>
</div>
<ng-template #noUser>
<p>No user found</p>
</ng-template>
`,
})
export class UserStatusComponent {
isLoggedIn = true;
username = 'Alice';
hasData = false;
}

*ngFor - List Rendering

@Component({
selector: 'app-product-list',
standalone: true,
imports: [CommonModule],
template: `
<ul>
<li *ngFor="let product of products">
{{ product.name }} - ${{ product.price }}
</li>
</ul>

<!-- With index -->
<ul>
<li *ngFor="let item of items; let i = index">
{{ i + 1 }}. {{ item }}
</li>
</ul>

<!-- With tracking (performance) -->
<div *ngFor="let user of users; trackBy: trackByUserId">
{{ user.name }}
</div>

<!-- Additional variables -->
<div *ngFor="let item of items;
let i = index;
let isFirst = first;
let isLast = last;
let isEven = even;
let isOdd = odd">
<span [class.first]="isFirst" [class.even]="isEven">
{{ i }}: {{ item }}
</span>
</div>
`
})
export class ProductListComponent {
products = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Mouse', price: 29 },
{ id: 3, name: 'Keyboard', price: 79 }
];

items = ['Item 1', 'Item 2', 'Item 3'];

users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];

trackByUserId(index: number, user: any) {
return user.id; // Track by unique identifier
}
}

*ngSwitch - Multiple Conditions

@Component({
selector: 'app-role-display',
standalone: true,
imports: [CommonModule],
template: `
<div [ngSwitch]="userRole">
<p *ngSwitchCase="'admin'">Admin Dashboard</p>
<p *ngSwitchCase="'user'">User Dashboard</p>
<p *ngSwitchCase="'guest'">Guest View</p>
<p *ngSwitchDefault>Unknown Role</p>
</div>
`,
})
export class RoleDisplayComponent {
userRole = 'admin';
}

Modern Control Flow (Angular 17+)

Angular 17 introduced built-in control flow syntax:

@Component({
selector: 'app-modern-controls',
standalone: true,
template: `
<!-- @if instead of *ngIf -->
@if (isLoggedIn) {
<p>Welcome, {{ username }}!</p>
} @else {
<p>Please log in</p>
}

<!-- @for instead of *ngFor -->
@for (product of products; track product.id) {
<div>{{ product.name }}</div>
} @empty {
<p>No products available</p>
}

<!-- @switch instead of ngSwitch -->
@switch (userRole) { @case ('admin') {
<p>Admin Panel</p>
} @case ('user') {
<p>User Panel</p>
} @default {
<p>Guest View</p>
} }
`,
})
export class ModernControlsComponent {
isLoggedIn = true;
username = 'Alice';
products = [
{ id: 1, name: 'Product 1' },
{ id: 2, name: 'Product 2' },
];
userRole = 'admin';
}

Attribute Directives

ngClass

@Component({
selector: 'app-status-badge',
standalone: true,
imports: [CommonModule],
template: `
<!-- Object syntax -->
<div
[ngClass]="{
active: isActive,
disabled: isDisabled,
premium: isPremium
}"
>
Status
</div>

<!-- Array syntax -->
<div [ngClass]="['btn', 'btn-primary', customClass]">Button</div>

<!-- String -->
<div [ngClass]="statusClass">Status</div>
`,
styles: [
`
.active {
color: green;
}
.disabled {
color: gray;
}
.premium {
font-weight: bold;
}
`,
],
})
export class StatusBadgeComponent {
isActive = true;
isDisabled = false;
isPremium = true;
customClass = 'large';
statusClass = 'active premium';
}

ngStyle

@Component({
selector: 'app-styled-box',
standalone: true,
imports: [CommonModule],
template: `
<div
[ngStyle]="{
'background-color': backgroundColor,
'width.px': width,
'height.px': height,
border: border
}"
>
Styled Box
</div>
`,
})
export class StyledBoxComponent {
backgroundColor = '#3b82f6';
width = 200;
height = 100;
border = '2px solid black';
}

Component Lifecycle Hooks

import {
Component,
OnInit,
OnDestroy,
OnChanges,
SimpleChanges,
} from '@angular/core';

@Component({
selector: 'app-lifecycle-demo',
standalone: true,
template: `<p>Lifecycle Demo</p>`,
})
export class LifecycleDemoComponent implements OnInit, OnDestroy, OnChanges {
// Called when component is created (before view initialization)
ngOnInit() {
console.log('Component initialized');
// Good place for: API calls, subscriptions
}

// Called when input properties change
ngOnChanges(changes: SimpleChanges) {
console.log('Input properties changed:', changes);
}

// Called when component is destroyed
ngOnDestroy() {
console.log('Component destroyed');
// Good place for: cleanup, unsubscribe
}
}

Best Practices

✅ Do

// Use OnPush change detection for performance
@Component({
selector: 'app-user-card',
changeDetection: ChangeDetectionStrategy.OnPush,
// ...
})

// Use trackBy with *ngFor
trackByUserId(index: number, user: User) {
return user.id;
}

// Keep templates clean and simple
// Move complex logic to component class

❌ Don't

// Don't use functions in templates (called every change detection)
// ❌ Bad
<div>{{ calculateTotal() }}</div>

// ✅ Good - use computed property or pipe
<div>{{ total }}</div>

// Don't mutate input properties
@Input() user!: User;
ngOnInit() {
this.user.name = 'New Name'; // ❌ Don't do this
}

Interview Questions

Q: What's the difference between {{}} and []? A: Interpolation {{}} is used to display text content. Property binding [] sets element properties and can bind to any property, not just text.

Q: What is the purpose of trackBy in *ngFor? A: TrackBy improves performance by helping Angular identify which items have changed, been added, or removed, preventing unnecessary DOM updates.

Q: What's the difference between *ngIf and [hidden]? A: *ngIf removes/adds elements from DOM. [hidden] just toggles CSS visibility. Use *ngIf for conditional rendering, [hidden] for frequent toggles.

Next Steps