Skip to content

Creating Components

Components are the fundamental building blocks of Angular applications. They encapsulate the template, logic, and styles for a part of the user interface, making your application modular, reusable, and maintainable.

Components allow you to break down complex UIs into smaller, manageable pieces. Each component owns its data and behavior, promoting separation of concerns and making your code easier to test, maintain, and reuse across your application.

Think of components as people in an office. Each person (component) has their own desk and files. But they need to talk to each other to get work done. Some talk by passing papers down the chain (parent → child). Others talk by raising their hand (events, child → parent). And some just peek over someone’s shoulder (ViewChild).

Component structure diagram

flowchart TB
subgraph Parent["Parent Component"]
PData["Data (from service or state)"]
PHandler["Handler method"]
end
subgraph Child["Child Component"]
Input["@Input() receives data"]
Output["@Output() emits events"]
end
PData -->|"Property binding [data]"| Input
Output -->|"Event binding (event)"| PHandler
style Parent fill:#4f46e5,color:#fff
style Child fill:#059669,color:#fff
style PData fill:#6366f1,color:#fff
style PHandler fill:#6366f1,color:#fff
style Input fill:#10b981,color:#fff
style Output fill:#10b981,color:#fff

Angular gives you clear ways for components to talk to each other.

Pass data DOWN from a parent component to a child component.

child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ userName }}</h3>
<p>Age: {{ userAge }}</p>
<span [class.active]="isActive">{{ isActive ? 'Online' : 'Offline' }}</span>
</div>
`
})
export class UserCardComponent {
@Input() userName: string = ''; // Required: parent MUST pass this
@Input() userAge: number = 0; // Optional with default
@Input({ required: true }) isActive: boolean = false; // Required input
}
// parent.component.html — Using the child component
<app-user-card
[userName]="'Alice'"
[userAge]="28"
[isActive]="true"
></app-user-card>

Renaming input properties:

@Component({ ... })
export class ProductPriceComponent {
@Input('product-price') price: number = 0; // Parent uses [product-price]
}
// parent.html
<app-product-price [product-price]="99.99"></app-product-price>

@Output + EventEmitter — Child → Parent

Section titled “@Output + EventEmitter — Child → Parent”

Child TELLS the parent something happened by emitting an event.

child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-star-rating',
template: `
<div class="stars">
<button *ngFor="let star of [1,2,3,4,5]"
(click)="rateProduct(star)"
[class.filled]="star <= currentRating">
{{ star <= currentRating ? '★' : '☆' }}
</button>
<p>Your rating: {{ currentRating }} / 5</p>
</div>
`
})
export class StarRatingComponent {
@Input() currentRating: number = 0;
@Output() ratingChange = new EventEmitter<number>(); // Child fires this
rateProduct(star: number): void {
this.currentRating = star;
this.ratingChange.emit(this.currentRating); // 👈 Send data UP to parent
}
}
// parent.component.ts — Listening to the child's event
@Component({
selector: 'app-product',
template: `
<app-star-rating
[currentRating]="productRating"
(ratingChange)="onRatingChanged($event)"
></app-star-rating>
<p *ngIf="lastRating">User rated: {{ lastRating }} stars</p>
`
})
export class ProductComponent {
productRating = 3;
lastRating: number | null = null;
onRatingChanged(newRating: number): void {
this.lastRating = newRating;
console.log('New rating from child:', newRating);
}
}

Two-way binding with banana-in-a-box [( )]:

@Component({ ... })
export class RatingComponent {
@Input() value: number = 0;
@Output() valueChange = new EventEmitter<number>();
// Name MUST be "valueChange" for "value" property
setRating(r: number): void {
this.value = r;
this.valueChange.emit(r);
}
}
// Parent can now use two-way binding!
<app-rating [(value)]="productRating"></app-rating>

@ViewChild — Parent Reads Child Directly

Section titled “@ViewChild — Parent Reads Child Directly”

Sometimes a parent needs to CALL a method on a child or read its properties directly.

child.component.ts
@Component({
selector: 'app-timer',
template: `<p>{{ seconds }}s</p>`
})
export class TimerComponent {
seconds = 0;
private intervalId: any;
start(): void {
this.intervalId = setInterval(() => this.seconds++, 1000);
}
stop(): void {
clearInterval(this.intervalId);
}
reset(): void {
this.seconds = 0;
}
}
// parent.component.ts
import { Component, ViewChild } from '@angular/core';
@Component({
selector: 'app-stopwatch',
template: `
<app-timer></app-timer> <!-- 👈 The child -->
<button (click)="start()">Start</button>
<button (click)="stop()">Stop</button>
<button (click)="reset()">Reset</button>
`
})
export class StopwatchComponent {
@ViewChild(TimerComponent) timer!: TimerComponent;
// 👆 Angular finds the <app-timer> in the template
start(): void { this.timer.start(); }
stop(): void { this.timer.stop(); }
reset(): void { this.timer.reset(); }
}

Put a hole in your component where the parent can inject custom content.

card.component.ts
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header">
<ng-content select="[card-title]"></ng-content>
<!-- Parent's content with card-title attribute goes here -->
</div>
<div class="card-body">
<ng-content></ng-content>
<!-- Default: parent content without a select goes here -->
</div>
<div class="card-footer">
<ng-content select="[card-actions]"></ng-content>
<!-- Parent's content with card-actions attribute goes here -->
</div>
</div>
`)
})
export class CardComponent { }
// parent.html — Using the card
<app-card>
<span card-title>User Profile</span>
<p>This is the main content of the card!</p>
<button card-actions>Save</button>
</app-card>

@ContentChild — Access Projected Content

Section titled “@ContentChild — Access Projected Content”
@Component({ ... })
export class TabComponent {
@ContentChild('tabContent') content!: ElementRef;
// Access the projected element that has #tabContent
ngAfterContentInit(): void {
console.log('Projected content:', this.content.nativeElement);
}
}
// ❌ Trying to use @ViewChild before view is ready
@Component({ ... })
export class BadComponent implements OnInit {
@ViewChild(TimerComponent) timer!: TimerComponent;
ngOnInit(): void {
this.timer.start(); // ❌ Undefined! View not ready yet!
}
}
// ✅ Use ngAfterViewInit instead
export class GoodComponent implements AfterViewInit {
@ViewChild(TimerComponent) timer!: TimerComponent;
ngAfterViewInit(): void {
this.timer.start(); // ✅ View is ready now
}
}
// ❌ Forgetting that @Input() required:true (Angular 16+)
@Input({ required: true }) name!: string; // Parent MUST pass this
  • @Input — Parent passes data DOWN to child. Like giving a note to someone.
  • @Output — Child sends event UP to parent. Like raising your hand.
  • @ViewChild — Parent reaches INTO child to call methods. Like tapping someone’s shoulder.
  • ng-content — Creates a hole for parent to inject custom HTML. Like a picture frame.
  • @ContentChild — Access the injected content from inside the component.
  • Always use ngAfterViewInit (not ngOnInit) to access @ViewChild — the view isn’t ready yet!

Here’s a simple component demonstrating basic usage:

import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div class="counter">
<p>Count: {{ count }}</p>
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>
</div>
`
})
export class CounterComponent {
@Input() initialValue: number = 0;
@Output() countChanged = new EventEmitter<number>();
count: number = 0;
ngOnInit() {
this.count = this.initialValue;
}
increment(): void {
this.count++;
this.countChanged.emit(this.count);
}
decrement(): void {
this.count--;
this.countChanged.emit(this.count);
}
}
  • Always use ngAfterViewInit (not ngOnInit) to access @ViewChild or @ContentChild — the view isn’t ready yet!
  • Use @Input() with { required: true } for mandatory inputs (Angular 16+)
  • Prefix event names with the property name for two-way binding (e.g., valueChange for value input)
  • Keep components focused on a single responsibility
  • Use @Output() to communicate changes from child to parent instead of direct mutation
  • Prefer @Input() and @Output() over @ViewChild for loose coupling when possible
  • Trying to use @ViewChild in ngOnInit instead of ngAfterViewInit
  • Forgetting to unsubscribe from observables in components (causing memory leaks)
  • Making components too large and doing too many things (violating single responsibility principle)
  • Mutating @Input() properties directly in child components (should be treated as read-only)
  • Forgetting to provide the correct selector when using @ViewChild with a component type
  • Using any type excessively instead of proper TypeScript typing
  1. What is the difference between @Input() and @Output() in Angular components?
  2. How does @ViewChild differ from @ContentChild?
  3. What lifecycle hook should you use to safely access @ViewChild and why?
  4. How do you implement two-way binding in a custom component?
  5. What is content projection (ng-content) and how do you use it?

Components are the building blocks of Angular applications. They encapsulate UI, logic, and styles, and communicate with each other through well-defined interfaces: @Input for receiving data from parents, @Output for sending events to parents, @ViewChild/@ContentChild for accessing child components or projected content, and ng-content for creating reusable component templates. Mastering these communication patterns is essential for building scalable and maintainable Angular applications.