Chapter 2: Components & Inputs
Components & Inputs — the part where your app starts feeling like real Lego blocks. We’ll go slow, with lots of examples, analogies, and code you can copy-paste right now.
Imagine you’re building a house:
- The parent component is like the living room.
- The child component is like a smart lamp.
- You want to tell the lamp: “Hey, shine with brightness 70 and color blue” → that’s passing data from parent → child using inputs.
In modern Angular (v19–21 as of Jan 2026), we almost always use the new signal-based inputs with input() instead of the old @Input() decorator. Why? It’s simpler, more reactive, plays perfectly with signals/computed/effect, and Angular can optimize change detection better.
1. What are Component Inputs?
Inputs are the official way a parent component gives data to a child component.
- Parent decides → child receives and uses (but cannot change the value directly — it’s read-only from child’s perspective).
- Classic name: “props” in React world.
- In Angular → traditionally @Input(), now input() function (recommended).
Key advantages of input():
- Returns a signal → automatic reactivity
- No need for ngOnChanges in 90% of cases
- Better tree-shaking & bundle size
- Required inputs give compile-time errors if forgotten
- Built-in transforms (e.g. string → number)
2. Creating a Reusable Child Component with Inputs
Let’s build something practical: a UserCard component that shows a person’s name, age, and avatar — and we’ll make it reusable.
Step 1: Generate the component
|
0 1 2 3 4 5 6 |
ng generate component user-card --standalone |
(or just create files manually)
src/app/user-card/user-card.component.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import { Component, input, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-user-card', standalone: true, imports: [CommonModule], template: ` <div class="card"> <img [src]="avatarUrl()" [alt]="name() + ' avatar'" class="avatar" /> <h3>{{ name() }}</h3> <p>Age: {{ age() }} years old</p> <p class="status" [class.adult]="isAdult()"> Status: {{ isAdult() ? 'Adult' : 'Minor' }} </p> </div> `, styles: [` .card { width: 280px; border: 1px solid #ccc; border-radius: 12px; padding: 16px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); text-align: center; margin: 16px; } .avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; } .adult { color: green; font-weight: bold; } `] }) export class UserCardComponent { // These are our inputs — all signal-based! name = input.required<string>(); // must be provided age = input.required<number>(); avatar = input<string>('https://i.pravatar.cc/120?u=default'); // optional + default // Derived / computed value — updates automatically when age changes isAdult = computed(() => this.age() >= 18); // Convenience signal if you want custom logic on avatar avatarUrl = computed(() => this.avatar() || 'https://i.pravatar.cc/120?u=fallback'); } |
Notice:
- input.required<T>() → TypeScript + Angular will complain at build/dev time if you forget to pass it
- input<T>(defaultValue) → optional, falls back to default
- We call them with () because they’re signals: name() reads current value
- computed() → auto-re-runs when any signal it reads changes
3. Using the Child in a Parent (Passing Data)
Now let’s use it in app.component.ts (or any parent).
src/app/app.component.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import { Component } from '@angular/core'; import { UserCardComponent } from './user-card/user-card.component'; @Component({ selector: 'app-root', standalone: true, imports: [UserCardComponent], template: ` <h1>Family Members</h1> <app-user-card [name]="'Amit Sharma'" [age]="28" [avatar]="'https://i.pravatar.cc/120?u=amit'" ></app-user-card> <app-user-card name="Priya Patel" <!-- no [ ] needed for strings if static --> [age]="16" ></app-user-card> <app-user-card [name]="dynamicUser.name" [age]="dynamicUser.age" avatar="https://i.pravatar.cc/120?u=dynamic" ></app-user-card> ` }) export class AppComponent { dynamicUser = { name: 'Rahul from Airoli', age: 32 }; } |
What happens here?
- [name]=”…” → property binding — passes the value into the input
- For static strings/numbers you can sometimes skip [ ] (Angular allows <app-user-card name=”Priya”>), but always use [ ] when the value is dynamic or from a variable
- If you forget a required input → dev server shows red error in console + browser warning
4. Aliases, Transforms, and Required in Practice
Sometimes you want:
- Different name in template vs code
- Auto-convert types (string “true” → boolean)
Example upgrade:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
// Inside UserCardComponent isActive = input(false, { transform: booleanAttribute }); // <user-card isActive> or isActive="true" → true priority = input(1, { transform: numberAttribute }); // "5" → 5 // Alias example (useful when name conflicts with HTML attribute) userName = input.required<string>({ alias: 'username' }); // in template: [username]="'John'" |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
<app-user-card username="John Doe" [age]="25" isActive priority="3" /> |
→ isActive() will be true (attribute presence = true)
5. Quick Rules of Thumb (like a teacher would pin on wall)
- Use input.required<…>() when the value must exist (prevents undefined bugs)
- Use input<…>(default) when it’s optional
- Read inputs with () — this.name() not this.name
- Derive everything possible with computed() — it’s free performance & clean code
- Never mutate input values directly — treat them as read-only (immutability = happiness)
- If child needs to “change” something → use outputs (next chapter)
6. Mini Practice Task (do this now!)
- Add a new optional input to UserCard: role = input<string>(‘Guest’);
- Show it in template: <p>Role: {{ role() || ‘Not specified’ }}</p>
- In AppComponent, create 3 different cards:
- One with role=”Developer”
- One without role
- One with role from a variable
- Bonus: Add highlight = input(false, {transform: booleanAttribute}) and if true → add background: #fff9c4; to .card in styles
Run ng serve → play with it!
Got questions? Want to see outputs next (child → parent communication), or debug something in your code? Just tell me — we’re building this step by step like real pair programming. 🚀
