Creating Components
Introduction
Section titled “Introduction”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.
Why do we need this?
Section titled “Why do we need this?”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.
Real-world analogy
Section titled “Real-world analogy”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).
Visual Explanation
Section titled “Visual Explanation”Mermaid Diagram
Section titled “Mermaid 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:#fffDeep Explanation
Section titled “Deep Explanation”Angular gives you clear ways for components to talk to each other.
@Input — Parent → Child
Section titled “@Input — Parent → Child”Pass data DOWN from a parent component to a child component.
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.
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.
@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.tsimport { 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(); }}Content Projection (ng-content)
Section titled “Content Projection (ng-content)”Put a hole in your component where the parent can inject custom content.
@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); }}🚨 Common Gotchas
Section titled “🚨 Common Gotchas”// ❌ 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 insteadexport 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 thisIn Simple Words 🧠
Section titled “In Simple Words 🧠”@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(notngOnInit) to access@ViewChild— the view isn’t ready yet!
Code Example
Section titled “Code Example”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); }}Best Practices
Section titled “Best Practices”- Always use
ngAfterViewInit(notngOnInit) to access@ViewChildor@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.,
valueChangeforvalueinput) - 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@ViewChildfor loose coupling when possible
Common Mistakes
Section titled “Common Mistakes”- Trying to use
@ViewChildinngOnInitinstead ofngAfterViewInit - 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
@ViewChildwith a component type - Using
anytype excessively instead of proper TypeScript typing
Interview Questions
Section titled “Interview Questions”- What is the difference between
@Input()and@Output()in Angular components? - How does
@ViewChilddiffer from@ContentChild? - What lifecycle hook should you use to safely access
@ViewChildand why? - How do you implement two-way binding in a custom component?
- What is content projection (
ng-content) and how do you use it?
Summary
Section titled “Summary”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.