Components & Templates¶
An Angular component has three parts: a TypeScript class (logic), an HTML template (view), and CSS (styling). Components are the building blocks of Angular apps. Data flows down via @Input() and up via @Output() EventEmitters. Directives extend HTML — structural (*ngIf, *ngFor) add/remove DOM, attribute directives ([ngClass], [ngStyle]) change appearance.
Component Structure¶
A component is defined with @Component decorator specifying selector (custom HTML tag), templateUrl (or inline template), and styleUrls. The class contains properties and methods. Components must be declared in an NgModule or be standalone.
Deep Dive: Example
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.css']
})
export class UserCardComponent {
@Input() user!: User; // from parent
@Output() delete = new EventEmitter<number>(); // to parent
onDelete() {
this.delete.emit(this.user.id);
}
}
<!-- user-card.component.html -->
<div class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button (click)="onDelete()">Delete</button>
</div>
<!-- Parent usage -->
<app-user-card
[user]="selectedUser"
(delete)="handleDelete($event)">
</app-user-card>
Data binding types:
| Syntax | Direction | Example |
|--------|-----------|---------|
| {{ }} | Component → Template | {{ user.name }} |
| [property] | Component → Template | [disabled]="isLoading" |
| (event) | Template → Component | (click)="save()" |
| [(ngModel)] | Two-way | [(ngModel)]="search" |
@Input and @Output¶
@Input() passes data from parent to child (property binding). @Output() emits events from child to parent using EventEmitter. This is Angular's one-way data flow — parent owns the data, child receives and notifies.
Deep Dive: Communication Patterns
// Child component
@Component({ selector: 'app-counter' })
export class CounterComponent {
@Input() count: number = 0; // receives from parent
@Output() countChange = new EventEmitter<number>(); // emits to parent
increment() {
this.count++;
this.countChange.emit(this.count);
}
}
// Parent template — two-way binding shorthand
<app-counter [(count)]="totalCount"></app-counter>
// Equivalent to:
<app-counter [count]="totalCount" (countChange)="totalCount = $event"></app-counter>
@Input with setter (react to changes):
Lifecycle Hooks¶
Key hooks in order: ngOnChanges (input property changes), ngOnInit (initialization — fetch data here), ngDoCheck (custom change detection), ngAfterViewInit (view rendered), ngOnDestroy (cleanup — unsubscribe observables, remove listeners). Most commonly used: ngOnInit and ngOnDestroy.
Deep Dive: All Hooks
export class UserComponent implements OnInit, OnChanges, OnDestroy {
@Input() userId!: number;
private subscription!: Subscription;
ngOnChanges(changes: SimpleChanges) {
// Called BEFORE ngOnInit and whenever @Input properties change
if (changes['userId']) {
console.log('Previous:', changes['userId'].previousValue);
console.log('Current:', changes['userId'].currentValue);
}
}
ngOnInit() {
// Called ONCE after first ngOnChanges
// Best place to fetch data, initialize logic
this.subscription = this.userService.getUser(this.userId)
.subscribe(user => this.user = user);
}
ngOnDestroy() {
// Cleanup: unsubscribe, clear timers, detach listeners
this.subscription.unsubscribe();
}
}
| Hook | When | Use For |
|---|---|---|
ngOnChanges |
Input changes | React to input property changes |
ngOnInit |
After first change detection | Fetch data, initialize |
ngDoCheck |
Every change detection | Custom change detection |
ngAfterContentInit |
After content projection | Access <ng-content> |
ngAfterViewInit |
After view renders | Access @ViewChild |
ngOnDestroy |
Before component removed | Cleanup subscriptions |
Change Detection¶
Angular checks component tree for changes after every async event (click, HTTP response, timer). Default strategy: checks entire component tree. OnPush strategy: only checks when @Input reference changes or an event fires in the component. Use OnPush for performance — forces immutable data patterns.
Deep Dive: Default vs OnPush
@Component({
selector: 'app-user',
changeDetection: ChangeDetectionStrategy.OnPush, // ← OnPush
template: `<p>{{ user.name }}</p>`
})
export class UserComponent {
@Input() user!: User;
}
OnPush triggers re-render only when:
1. @Input() reference changes (not mutation!)
2. Event handler fires in the component
3. Observable used with async pipe emits
4. ChangeDetectorRef.markForCheck() is called manually
Structural Directives¶
Structural directives add/remove DOM elements: *ngIf (conditional rendering), *ngFor (loop), *ngSwitch (switch-case). The * is syntactic sugar for <ng-template>. Only one structural directive per element.
Deep Dive: Examples
<!-- *ngIf — conditional rendering -->
<div *ngIf="isLoggedIn; else loginTemplate">
Welcome, {{ user.name }}!
</div>
<ng-template #loginTemplate>
<button (click)="login()">Log In</button>
</ng-template>
<!-- *ngFor — loop with trackBy for performance -->
<ul>
<li *ngFor="let user of users; let i = index; trackBy: trackByUserId">
{{ i + 1 }}. {{ user.name }}
</li>
</ul>
<!-- *ngSwitch -->
<div [ngSwitch]="status">
<p *ngSwitchCase="'active'">Active</p>
<p *ngSwitchCase="'inactive'">Inactive</p>
<p *ngSwitchDefault>Unknown</p>
</div>
Attribute Directives¶
Attribute directives change appearance or behavior without adding/removing elements: [ngClass] (dynamic CSS classes), [ngStyle] (dynamic styles). You can create custom attribute directives with @Directive.
Deep Dive: Built-in & Custom
<!-- ngClass — conditional CSS classes -->
<div [ngClass]="{ 'active': isActive, 'error': hasError }">Status</div>
<!-- ngStyle — dynamic inline styles -->
<p [ngStyle]="{ 'color': isError ? 'red' : 'green', 'font-size': fontSize + 'px' }">
Message
</p>
Custom directive:
@Directive({ selector: '[appHighlight]' })
export class HighlightDirective {
@Input() appHighlight = 'yellow';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.el.nativeElement.style.backgroundColor = this.appHighlight;
}
@HostListener('mouseleave') onMouseLeave() {
this.el.nativeElement.style.backgroundColor = '';
}
}
// Usage: <p [appHighlight]="'cyan'">Hover me</p>
Dependency Injection¶
Angular has a built-in DI container. Services are decorated with @Injectable({ providedIn: 'root' }) — making them singletons app-wide. Inject via constructor. DI hierarchy: root (single instance), module-level (per module), component-level (per component instance).
Deep Dive: Providers & Injection
// Service
@Injectable({ providedIn: 'root' }) // singleton, tree-shakable
export class UserService {
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
}
// Component — inject via constructor
@Component({ selector: 'app-users' })
export class UsersComponent {
constructor(private userService: UserService) {} // injected!
}
// Component-level provider — new instance per component
@Component({
selector: 'app-counter',
providers: [CounterService] // scoped to this component
})
export class CounterComponent { }
Pipes¶
Pipes transform displayed values in templates: {{ value | pipeName }}. Built-in: date, currency, uppercase, json, async. Custom pipes with @Pipe. Pure pipes (default) only run when input reference changes. Impure pipes run on every change detection — use sparingly.
Deep Dive: Built-in & Custom Pipes
{{ user.createdAt | date:'short' }}
{{ price | currency:'USD' }}
{{ name | uppercase }}
{{ data | json }}
{{ users$ | async }} <!-- auto-subscribes & unsubscribes! -->
Custom pipe:
Common Interview Questions¶
Common Interview Questions
- What is a component in Angular? What are its parts?
- How do you pass data between parent and child components?
- What are lifecycle hooks? Which ones do you use most?
- What is change detection? Default vs OnPush?
- What is the difference between structural and attribute directives?
- What is dependency injection in Angular?
- What is
trackByin*ngForand why is it important? - What are pipes? Pure vs impure?
- What is the
asyncpipe?