What Are Components?
Components are the fundamental building blocks of Angular applications. Every piece of UI you see is a component — the header, a product card, a form, a modal. Components are composed together to form the complete application.
A component consists of three parts:
- Class (TypeScript) — logic and data
- Template (HTML) — the view/markup
- Styles (CSS) — scoped styling
Creating a Component
Using the CLI
ng generate component user-profile
# Shorthand: ng g c user-profileThis creates:
src/app/user-profile/
├── user-profile.component.ts
├── user-profile.component.html
├── user-profile.component.css
└── user-profile.component.spec.tsManual Creation
import { Component } from '@angular/core';
@Component({
selector: 'app-user-profile',
standalone: true,
templateUrl: './user-profile.component.html',
styleUrl: './user-profile.component.css',
})
export class UserProfileComponent {
name = 'John Doe';
role = 'Developer';
}Inline vs External Templates
Inline Template
Best for small components:
@Component({
selector: 'app-badge',
standalone: true,
template: `
<span class="badge" [class]="type">
{{ label }}
</span>
`,
styles: [`
.badge { padding: 4px 12px; border-radius: 20px; font-size: 12px; }
.success { background: #d4edda; color: #155724; }
.warning { background: #fff3cd; color: #856404; }
`],
})
export class BadgeComponent {
label = 'Active';
type = 'success';
}External Template
Best for larger components:
@Component({
selector: 'app-dashboard',
standalone: true,
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.css',
})
export class DashboardComponent {
// ...
}Component Lifecycle Hooks
Angular components go through a lifecycle from creation to destruction. You can hook into these phases:
| Hook | When Called | Common Use |
|---|---|---|
ngOnInit | After first data-bound properties set | Fetch data, initialize logic |
ngOnChanges | When input properties change | React to input changes |
ngDoCheck | Every change detection run | Custom change detection |
ngAfterViewInit | After view (DOM) is initialized | Access DOM elements |
ngAfterContentInit | After content projection | Access projected content |
ngOnDestroy | Before component is destroyed | Cleanup subscriptions, timers |
Using Lifecycle Hooks
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({
selector: 'app-timer',
standalone: true,
template: `<p>Elapsed: {{ seconds }}s</p>`,
})
export class TimerComponent implements OnInit, OnDestroy {
seconds = 0;
private subscription!: Subscription;
ngOnInit() {
console.log('Timer component initialized');
this.subscription = interval(1000).subscribe(() => {
this.seconds++;
});
}
ngOnDestroy() {
console.log('Timer component destroyed — cleaning up');
this.subscription.unsubscribe();
}
}Component Interaction
Parent → Child with @Input
The parent passes data down to a child:
// child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
`,
})
export class UserCardComponent {
@Input({ required: true }) user!: { name: string; email: string };
}<!-- parent.component.html -->
<app-user-card [user]="selectedUser" />Child → Parent with @Output
The child emits events up to the parent:
// child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-search-box',
standalone: true,
template: `
<input #searchInput type="text" placeholder="Search...">
<button (click)="search.emit(searchInput.value)">Search</button>
`,
})
export class SearchBoxComponent {
@Output() search = new EventEmitter<string>();
}<!-- parent.component.html -->
<app-search-box (search)="onSearch($event)" />// parent.component.ts
export class ParentComponent {
onSearch(query: string) {
console.log('Searching for:', query);
}
}Input Transforms (Angular 16+)
import { Component, Input, booleanAttribute, numberAttribute } from '@angular/core';
@Component({
selector: 'app-alert',
standalone: true,
template: `
@if (visible) {
<div class="alert" [class]="type">{{ message }}</div>
}
`,
})
export class AlertComponent {
@Input() message = '';
@Input() type: 'info' | 'warning' | 'error' = 'info';
@Input({ transform: booleanAttribute }) visible = true;
@Input({ transform: numberAttribute }) duration = 5000;
}<!-- Usage: these string attributes are auto-transformed -->
<app-alert message="Saved!" visible duration="3000" />Content Projection (ng-content)
Content projection lets a parent inject content into a child component's template:
// card.component.ts
@Component({
selector: 'app-card',
standalone: true,
template: `
<div class="card">
<div class="card-header">
<ng-content select="[card-title]" />
</div>
<div class="card-body">
<ng-content />
</div>
<div class="card-footer">
<ng-content select="[card-actions]" />
</div>
</div>
`,
})
export class CardComponent {}<!-- Usage -->
<app-card>
<h3 card-title>My Card</h3>
<p>This is the card body content.</p>
<div card-actions>
<button>Save</button>
<button>Cancel</button>
</div>
</app-card>ViewChild — Accessing Child Elements
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-focus-input',
standalone: true,
template: `<input #myInput type="text" placeholder="Auto-focused">`,
})
export class FocusInputComponent implements AfterViewInit {
@ViewChild('myInput') inputRef!: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
this.inputRef.nativeElement.focus();
}
}Component Best Practices
- Keep components small and focused — each component should do one thing well
- Use standalone components — they are the modern default (no NgModule needed)
- Use OnPush change detection for performance:
typescript
@Component({ changeDetection: ChangeDetectionStrategy.OnPush, // ... }) - Unsubscribe from Observables in
ngOnDestroyto prevent memory leaks - **Use
@Input({ required: true })** for mandatory inputs - Prefix selectors — use
app-prefix (or your org prefix) for all component selectors
Next Steps
Components need content to display. Next, we'll explore templates and data binding in depth — the powerful template syntax that connects your component class to the HTML view.