π‘ Data Binding & Component Communication
Essential Angular Interview Topic #3
Interview Frequency: 95% | Experience Level: All Levels | Company Tiers: All**
"Data binding is the heart of Angular. Master this, and you master Angular's reactive philosophy." - Angular Team
π― Interview Success Framework
What Interviewers Really Test
- Junior (0-2 years): Basic binding syntax, simple @Input/@Output, understanding of interpolation vs property binding
- Mid-Level (2-5 years): Complex communication patterns, performance optimization, OnPush strategy, proper subscription management
- Senior (5+ years): Architecture decisions, change detection mastery, custom two-way binding, memory leak prevention
Company-Tier Expectations
- Tier 1 (FAANG): Deep understanding of change detection cycles, Zone.js internals, micro-optimizations, and scalable communication patterns
- Tier 2 (Professional): Practical implementation with real-world scenarios, best practices, debugging skills, and team-ready code
- Tier 3 (Community): Solid fundamentals with clear explanations, basic patterns, and willingness to learn advanced concepts
Red Flags for Interviewers β
- Not knowing the difference between interpolation and property binding
- Creating memory leaks with unmanaged subscriptions
- Using ViewChild in ngOnInit instead of ngAfterViewInit
- Mutating @Input properties directly in child components
- Not understanding when OnPush strategy breaks updates
οΏ½ THEORETICAL FOUNDATION (Understanding the Why)
What is Data Binding?
Data binding is Angular's mechanism to synchronize data between the component class (TypeScript) and the template (HTML). It creates a bridge that automatically updates the view when data changes and can capture user interactions to update the component state.
Why Data Binding Matters
- Eliminates Manual DOM Manipulation: No need for
document.getElementById()or jQuery - Reactive UI Updates: Views automatically reflect data changes
- Type Safety: TypeScript integration prevents runtime errors
- Performance: Angular's change detection optimizes updates
Angular's Data Flow Architecture
βββββββββββββββββββ Data Binding βββββββββββββββββββ
β Component β βββββββββββββββββββ β Template β
β (TypeScript) β β (HTML) β
β β β β
β β’ Properties β ββββββ Binding βββ β β’ Interpolation β
β β’ Methods β β β’ Directives β
β β’ Events β βββββ Events βββββ β β’ User Input β
βββββββββββββββββββ βββββββββββββββββββ
Data Binding vs Traditional JavaScript
// Traditional JavaScript (Manual DOM)
document.getElementById('username').textContent = user.name;
document.getElementById('submit-btn').disabled = !isFormValid;
// Angular Data Binding (Declarative)
// Template: {{ user.name }} and [disabled]="!isFormValid"
// Angular handles the DOM updates automatically!
Component Communication Theory
Angular applications are component trees. Components need to communicate:
- Hierarchical Communication: Parent β Child
- Sibling Communication: Via shared services
- Cross-Component Communication: Via state management
βββββββββββββββββββββββββββββββββββββββββββ
β App Component β
β βββββββββββββββββββ ββββββββββββββββββββ
β β Header β β Main Content ββ
β β βββββββββββββ β β βββββββββββββ ββ
β β βNavigation β β β β Article β ββ
β β βββββββββββββ β β β βββββββββ β ββ
β βββββββββββββββββββ β β βCommentβ β ββ
β β β βββββββββ β ββ
β β βββββββββββββββββββ
β ββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
Change Detection Fundamentals
Angular uses Zone.js to detect when data might have changed:
// These operations trigger change detection:
setTimeout(() => this.data = newData, 1000); // Async operations
this.http.get('/api/data').subscribe(); // HTTP requests
button.addEventListener('click', handler); // DOM events
Promise.resolve().then(() => update()); // Promises
// Angular automatically checks and updates the DOM!
Performance Considerations
- Default Strategy: Checks all components on every change
- OnPush Strategy: Only checks when inputs change or events occur
- Immutable Data: Helps Angular optimize change detection
οΏ½π₯ CORE CONCEPTS (Must-Know for All Interviews)
1. Data Binding Types βββ
Interpolation ({{ }})
// Component
export class UserComponent {
userName = 'John Doe';
userAge = 30;
isLoggedIn = true;
getUserGreeting() {
return `Hello, ${this.userName}!`;
}
getStatusMessage() {
return this.isLoggedIn ? 'Welcome back!' : 'Please log in';
}
}
<!-- Template - Interpolation Examples -->
<h1>{{ userName }}</h1> <!-- String interpolation -->
<p>{{ getUserGreeting() }}</p> <!-- Method call -->
<div>Age: {{ userAge }} years old</div> <!-- Number -->
<span>{{ 2 + 2 }}</span> <!-- Expression evaluation -->
<p>{{ isLoggedIn ? 'Online' : 'Offline' }}</p> <!-- Ternary operator -->
<!-- Advanced interpolation -->
<div>{{ user?.profile?.displayName || 'Anonymous' }}</div> <!-- Safe navigation -->
<span>{{ items.length > 0 ? items[0].name : 'No items' }}</span> <!-- Array access -->
π― Interview Q: "What's the difference between interpolation and property binding?"
β
Answer:
- Interpolation ({{ }}) converts result to string and is used for text content
- Property binding ([property]) preserves data types and binds to element/component properties
- Performance: Property binding is slightly faster as it skips string conversion
π― Interview Q: "What happens if interpolation expression throws an error?" β Answer: Angular displays empty string and logs error to console in development mode
Property Binding ([property])
// Component
export class ImageComponent {
imageUrl = 'assets/logo.png';
isDisabled = false;
userObject = { name: 'John', age: 30 };
cssClasses = ['highlight', 'active'];
// Dynamic properties
get buttonColor() {
return this.isDisabled ? 'gray' : 'blue';
}
// Complex object binding
formConfig = {
placeholder: 'Enter your name',
maxLength: 50,
required: true
};
}
<!-- Template - Property Binding Examples -->
<!-- Basic property binding -->
<img [src]="imageUrl" [alt]="userName">
<button [disabled]="isDisabled" [style.color]="buttonColor">Click me</button>
<!-- Object and array binding -->
<app-user [userData]="userObject"></app-user>
<div [class]="cssClasses"></div>
<!-- Attribute binding (for non-standard attributes) -->
<div [attr.aria-label]="buttonLabel" [attr.data-id]="userId"></div>
<!-- Style binding -->
<div [style.width.px]="containerWidth"
[style.background-color]="isActive ? 'green' : 'red'"></div>
<!-- Class binding -->
<div [class.active]="isActive"
[class.disabled]="isDisabled"
[ngClass]="{'error': hasError, 'warning': hasWarning}"></div>
<!-- Complex form binding -->
<input [placeholder]="formConfig.placeholder"
[maxlength]="formConfig.maxLength"
[required]="formConfig.required">
<!-- Common Pitfalls to Avoid -->
<!-- β Wrong: Mixing interpolation with property binding -->
<!-- <img src="{{ imageUrl }}" alt="{{ userName }}"> -->
<!-- β
Correct: Use property binding for non-string values -->
<img [src]="imageUrl" [alt]="userName">
<!-- β Wrong: Property binding for text content -->
<!-- <span [textContent]="message"></span> -->
<!-- β
Correct: Use interpolation for text -->
<span>{{ message }}</span>
π― Interview Q: "When should you use attribute binding vs property binding?"
β
Answer:
- Property binding ([property]) for standard DOM properties (src, disabled, value)
- Attribute binding ([attr.attribute]) for HTML attributes with no DOM property (aria-, data-, colspan)
π― Interview Q: "What's the difference between [class] and [ngClass]?"
β
Answer:
- [class] binds string/array directly to class attribute
- [ngClass] accepts object with conditional logic: {className: condition}
Event Binding ((event))
// Component
export class ButtonComponent {
clickCount = 0;
userInput = '';
isProcessing = false;
onButtonClick(event: MouseEvent) {
this.clickCount++;
console.log('Button clicked:', event.target);
console.log('Click coordinates:', event.clientX, event.clientY);
}
onInputChange(event: Event) {
const target = event.target as HTMLInputElement;
this.userInput = target.value;
this.validateInput(target.value);
}
onKeyPress(event: KeyboardEvent) {
if (event.key === 'Enter') {
this.onSubmit();
} else if (event.key === 'Escape') {
this.onCancel();
}
}
onSubmit() {
if (!this.isProcessing) {
this.isProcessing = true;
// Simulate async operation
setTimeout(() => {
this.isProcessing = false;
console.log('Form submitted:', this.userInput);
}, 1000);
}
}
onCancel() {
this.userInput = '';
this.clickCount = 0;
}
onFileSelected(event: Event) {
const target = event.target as HTMLInputElement;
const files = target.files;
if (files && files.length > 0) {
console.log('File selected:', files[0].name);
}
}
onMouseEvents(event: MouseEvent) {
console.log(`Mouse ${event.type} at (${event.clientX}, ${event.clientY})`);
}
private validateInput(value: string) {
// Validation logic
console.log('Validating:', value);
}
}
<!-- Template - Event Binding Examples -->
<!-- Basic event binding -->
<button (click)="onButtonClick($event)">
Clicked {{ clickCount }} times
</button>
<!-- Input events -->
<input (input)="onInputChange($event)"
(keyup.enter)="onSubmit()"
(keyup.escape)="onCancel()"
(blur)="validateInput(userInput)"
placeholder="Type and press Enter...">
<!-- Keyboard events with modifiers -->
<input (keyup.ctrl.s)="saveDocument()"
(keyup.shift.enter)="insertLineBreak()"
(keydown.delete)="deleteSelected()">
<!-- Mouse events -->
<div (mouseenter)="onMouseEvents($event)"
(mouseleave)="onMouseEvents($event)"
(click)="onMouseEvents($event)"
class="interactive-area">
Hover or click me
</div>
<!-- Form events -->
<form (submit)="onSubmit()" (reset)="onCancel()">
<input type="text" [(ngModel)]="userInput">
<input type="file" (change)="onFileSelected($event)">
<button type="submit" [disabled]="isProcessing">
{{ isProcessing ? 'Processing...' : 'Submit' }}
</button>
</form>
<!-- Advanced event binding -->
<button (click)="clickCount = clickCount + 1">
Increment ({{ clickCount }})
</button>
<!-- Multiple event handlers -->
<input (focus)="onFocus(); trackAnalytics('input_focus')"
(blur)="onBlur(); saveProgress()">
<!-- Preventing default behavior -->
<a href="https://example.com"
(click)="handleLinkClick($event)">
Custom Link Handler
</a>
<!-- Custom events from child components -->
<app-user-form (userSaved)="onUserSaved($event)"
(validationError)="onValidationError($event)">
</app-user-form>
π― Interview Q: "What's $event in Angular event binding?"
β
Answer:
- $event is the actual DOM event object (MouseEvent, KeyboardEvent, etc.)
- Contains event properties like target, currentTarget, preventDefault(), stopPropagation()
- For custom events from child components, it contains the emitted value
π― Interview Q: "How do you prevent event bubbling in Angular?" β Answer:
onButtonClick(event: MouseEvent) {
event.stopPropagation(); // Prevent bubbling
event.preventDefault(); // Prevent default behavior
}
π― Interview Q: "What's the difference between (input) and (change) events?"
β
Answer:
- (input) fires on every keystroke/value change
- (change) fires only when element loses focus and value has changed
Two-Way Data Binding ([(ngModel)])
// Component
export class FormComponent {
userName = '';
userEmail = '';
// Custom two-way binding
private _count = 0;
@Input() count = 0;
@Output() countChange = new EventEmitter<number>();
updateCount(newCount: number) {
this.count = newCount;
this.countChange.emit(newCount);
}
}
<!-- Built-in two-way binding -->
<input [(ngModel)]="userName" placeholder="Your name">
<p>Hello, {{ userName }}!</p>
<!-- Custom two-way binding -->
<app-counter [(count)]="totalCount"></app-counter>
<!-- Manual two-way binding (equivalent) -->
<input [ngModel]="userName" (ngModelChange)="userName=$event">
π REAL-WORLD SCENARIOS (Production Examples)
E-Commerce Product Card Component
// Real-world example: Product listing with complex interactions
@Component({
selector: 'app-product-card',
template: `
<div class="product-card"
[class.featured]="product.isFeatured"
[class.out-of-stock]="product.stock === 0">
<!-- Image with lazy loading -->
<img [src]="product.imageUrl"
[alt]="product.name"
[loading]="isInViewport ? 'eager' : 'lazy'"
(load)="onImageLoaded()"
(error)="onImageError()">
<!-- Product info -->
<h3>{{ product.name | titlecase }}</h3>
<p class="price">
<span [class.discounted]="product.originalPrice !== product.currentPrice">
{{ product.currentPrice | currency }}
</span>
<span *ngIf="product.originalPrice !== product.currentPrice" class="original-price">
{{ product.originalPrice | currency }}
</span>
</p>
<!-- Stock indicator -->
<div class="stock-info" [ngSwitch]="getStockStatus()">
<span *ngSwitchCase="'in-stock'" class="in-stock">
β
{{ product.stock }} available
</span>
<span *ngSwitchCase="'low-stock'" class="low-stock">
β οΈ Only {{ product.stock }} left
</span>
<span *ngSwitchCase="'out-of-stock'" class="out-of-stock">
β Out of stock
</span>
</div>
<!-- Interactive buttons -->
<div class="actions">
<button [disabled]="product.stock === 0 || isAddingToCart"
(click)="addToCart(product)"
[attr.aria-label]="'Add ' + product.name + ' to cart'">
{{ isAddingToCart ? 'Adding...' : 'Add to Cart' }}
</button>
<button class="wishlist"
[class.active]="isInWishlist"
(click)="toggleWishlist()"
[attr.aria-pressed]="isInWishlist">
{{ isInWishlist ? 'β€οΈ' : 'π€' }}
</button>
</div>
<!-- Quick view on hover -->
<div class="quick-actions"
*ngIf="showQuickActions"
(mouseenter)="showQuickActions = true"
(mouseleave)="showQuickActions = false">
<button (click)="quickView.emit(product)">Quick View</button>
<button (click)="compare.emit(product)">Compare</button>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
@Input({ required: true }) product!: Product;
@Input() isInViewport = false;
@Output() addToCart = new EventEmitter<Product>();
@Output() wishlistToggle = new EventEmitter<{product: Product, isAdded: boolean}>();
@Output() quickView = new EventEmitter<Product>();
@Output() compare = new EventEmitter<Product>();
isAddingToCart = false;
isInWishlist = false;
showQuickActions = false;
constructor(private cdr: ChangeDetectorRef) {}
getStockStatus(): 'in-stock' | 'low-stock' | 'out-of-stock' {
if (this.product.stock === 0) return 'out-of-stock';
if (this.product.stock <= 5) return 'low-stock';
return 'in-stock';
}
onAddToCart() {
this.isAddingToCart = true;
this.addToCart.emit(this.product);
// Simulate API call
setTimeout(() => {
this.isAddingToCart = false;
this.cdr.detectChanges(); // Manual trigger for OnPush
}, 1000);
}
toggleWishlist() {
this.isInWishlist = !this.isInWishlist;
this.wishlistToggle.emit({
product: this.product,
isAdded: this.isInWishlist
});
}
onImageLoaded() {
console.log('Image loaded for:', this.product.name);
}
onImageError() {
// Fallback image logic
console.error('Image failed to load for:', this.product.name);
}
}
Smart Form Component with Validation
// Real-world form with complex validation and communication
@Component({
selector: 'app-user-registration',
template: `
<form [formGroup]="registrationForm"
(ngSubmit)="onSubmit()"
novalidate>
<!-- Username field with real-time validation -->
<div class="field-group">
<label for="username">Username</label>
<input id="username"
formControlName="username"
[class.error]="hasError('username')"
(blur)="checkUsernameAvailability()"
placeholder="Enter username">
<div class="field-status">
<span *ngIf="isCheckingUsername" class="checking">
π Checking availability...
</span>
<span *ngIf="hasError('username', 'required')" class="error">
Username is required
</span>
<span *ngIf="hasError('username', 'minlength')" class="error">
Username must be at least 3 characters
</span>
<span *ngIf="hasError('username', 'usernameExists')" class="error">
Username is already taken
</span>
<span *ngIf="isUsernameValid" class="success">
β
Username is available
</span>
</div>
</div>
<!-- Email with pattern validation -->
<div class="field-group">
<label for="email">Email</label>
<input id="email"
type="email"
formControlName="email"
[class.error]="hasError('email')"
placeholder="your@email.com">
<div *ngIf="hasError('email')" class="error-messages">
<div *ngIf="hasError('email', 'required')">Email is required</div>
<div *ngIf="hasError('email', 'email')">Please enter a valid email</div>
</div>
</div>
<!-- Password with strength indicator -->
<div class="field-group">
<label for="password">Password</label>
<input id="password"
type="password"
formControlName="password"
[class.error]="hasError('password')"
(input)="updatePasswordStrength()"
placeholder="Enter password">
<div class="password-strength" *ngIf="passwordStrength">
<div class="strength-bar">
<div class="strength-fill"
[style.width.%]="passwordStrength.percentage"
[class]="passwordStrength.level"></div>
</div>
<span class="strength-text">{{ passwordStrength.text }}</span>
</div>
</div>
<!-- Terms and conditions -->
<div class="checkbox-group">
<label>
<input type="checkbox"
formControlName="agreeToTerms"
[class.error]="hasError('agreeToTerms')">
I agree to the
<a href="/terms"
(click)="openTerms($event)"
target="_blank">Terms and Conditions</a>
</label>
<div *ngIf="hasError('agreeToTerms')" class="error">
You must agree to the terms
</div>
</div>
<!-- Submit button with loading state -->
<button type="submit"
[disabled]="registrationForm.invalid || isSubmitting"
[class.loading]="isSubmitting">
<span *ngIf="!isSubmitting">Create Account</span>
<span *ngIf="isSubmitting">
<span class="spinner"></span> Creating Account...
</span>
</button>
<!-- Form summary for debugging (dev only) -->
<div *ngIf="showDebugInfo" class="debug-info">
<h4>Form Debug Info:</h4>
<p>Form Valid: {{ registrationForm.valid }}</p>
<p>Form Value: {{ registrationForm.value | json }}</p>
<p>Form Errors: {{ getFormErrors() | json }}</p>
</div>
</form>
`
})
export class UserRegistrationComponent implements OnInit, OnDestroy {
@Output() userRegistered = new EventEmitter<User>();
@Output() registrationError = new EventEmitter<string>();
registrationForm!: FormGroup;
isSubmitting = false;
isCheckingUsername = false;
isUsernameValid = false;
passwordStrength: PasswordStrength | null = null;
showDebugInfo = false; // Set to true in development
private destroy$ = new Subject<void>();
constructor(
private fb: FormBuilder,
private userService: UserService,
private cdr: ChangeDetectorRef
) {}
ngOnInit() {
this.createForm();
this.setupFormWatchers();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
private createForm() {
this.registrationForm = this.fb.group({
username: ['', [
Validators.required,
Validators.minLength(3),
this.usernameValidator
], [this.usernameExistsValidator.bind(this)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, this.passwordStrengthValidator]],
agreeToTerms: [false, Validators.requiredTrue]
});
}
private setupFormWatchers() {
// Watch for form changes and update UI accordingly
this.registrationForm.statusChanges
.pipe(takeUntil(this.destroy$))
.subscribe(status => {
// Update component state based on form status
this.cdr.detectChanges();
});
}
hasError(fieldName: string, errorType?: string): boolean {
const field = this.registrationForm.get(fieldName);
if (!field) return false;
if (errorType) {
return field.hasError(errorType) && (field.dirty || field.touched);
}
return field.invalid && (field.dirty || field.touched);
}
checkUsernameAvailability() {
const username = this.registrationForm.get('username')?.value;
if (username && username.length >= 3) {
this.isCheckingUsername = true;
this.userService.checkUsernameAvailability(username)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (available) => {
this.isUsernameValid = available;
this.isCheckingUsername = false;
this.cdr.detectChanges();
},
error: () => {
this.isCheckingUsername = false;
this.cdr.detectChanges();
}
});
}
}
updatePasswordStrength() {
const password = this.registrationForm.get('password')?.value || '';
this.passwordStrength = this.calculatePasswordStrength(password);
}
onSubmit() {
if (this.registrationForm.valid) {
this.isSubmitting = true;
const userData = this.registrationForm.value;
this.userService.registerUser(userData)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (user) => {
this.userRegistered.emit(user);
this.isSubmitting = false;
},
error: (error) => {
this.registrationError.emit(error.message);
this.isSubmitting = false;
}
});
}
}
openTerms(event: MouseEvent) {
event.preventDefault();
// Open terms modal or navigate to terms page
console.log('Opening terms and conditions');
}
// Custom validators
private usernameValidator(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
const hasValidChars = /^[a-zA-Z0-9_]+$/.test(value);
return hasValidChars ? null : { invalidCharacters: true };
}
private usernameExistsValidator(control: AbstractControl): Observable<ValidationErrors | null> {
if (!control.value) {
return of(null);
}
return this.userService.checkUsernameAvailability(control.value).pipe(
map(available => available ? null : { usernameExists: true }),
catchError(() => of(null))
);
}
private passwordStrengthValidator(control: AbstractControl): ValidationErrors | null {
const password = control.value;
if (!password) return null;
const strength = this.calculatePasswordStrength(password);
return strength.percentage < 50 ? { weakPassword: true } : null;
}
private calculatePasswordStrength(password: string): PasswordStrength {
let score = 0;
let text = '';
if (password.length >= 8) score += 25;
if (/[a-z]/.test(password)) score += 25;
if (/[A-Z]/.test(password)) score += 25;
if (/[0-9]/.test(password)) score += 25;
if (/[^a-zA-Z0-9]/.test(password)) score += 25;
if (score < 50) {
text = 'Weak';
} else if (score < 75) {
text = 'Medium';
} else {
text = 'Strong';
}
return {
percentage: Math.min(score, 100),
level: text.toLowerCase(),
text
};
}
getFormErrors(): any {
const errors: any = {};
Object.keys(this.registrationForm.controls).forEach(key => {
const control = this.registrationForm.get(key);
if (control && control.errors) {
errors[key] = control.errors;
}
});
return errors;
}
}
interface PasswordStrength {
percentage: number;
level: string;
text: string;
}
π COMPONENT COMMUNICATION THEORY
Why Components Need to Communicate
In real applications, components must share data and coordinate actions:
- E-commerce Example: Product list β Product details β Shopping cart
- Form Example: Input validation β Form status β Submit button state
- Dashboard Example: Filter controls β Data display β Export functionality
Communication Patterns Hierarchy
1. π Direct Parent-Child (@Input/@Output)
βββ Simplest and most performant
βββ Type-safe with TypeScript
βββ Ideal for close relationships
2. π Template References (#templateVar)
βββ Direct access to child components/elements
βββ Limited to template scope
βββ Good for simple interactions
3. π― ViewChild/ViewChildren (Programmatic access)
βββ Access child components in TypeScript
βββ Available after view initialization
βββ Useful for complex component control
4. π Service-Based Communication
βββ For distant or sibling components
βββ Scalable for complex applications
βββ Requires careful memory management
Data Flow Principles
// β
GOOD: Unidirectional data flow
Parent ββ@Inputβββ Child
Parent ββ@Outputβββ Child
// β AVOID: Bidirectional data mutation
Parent βββshared object mutationβββ Child
// This makes debugging difficult and breaks change detection
When to Use Each Pattern
| Pattern | Use Case | Performance | Complexity |
|---|---|---|---|
@Input/@Output |
Parent-Child | βββ | β |
| Template Ref | Simple interactions | βββ | β |
ViewChild |
Programmatic control | ββ | ββ |
| Services | Distant components | β | βββ |
| State Management | Complex apps | β | ββββ |
π COMPONENT COMMUNICATION PATTERNS
2. Parent to Child: @Input βββ
Basic @Input Usage
// Child Component
export class UserCardComponent {
@Input() user!: User; // Required input (Angular 16+)
@Input() showEmail = true; // Optional with default
@Input({ required: true }) userId!: string; // Explicit required
// Input with transformation
@Input({ transform: (value: string) => value.toUpperCase() })
displayName!: string;
}
<!-- Parent Template -->
<app-user-card
[user]="selectedUser"
[showEmail]="isAdmin"
[userId]="user.id"
[displayName]="user.name">
</app-user-card>
Advanced @Input Patterns
// Input validation and transformation
export class ProductComponent {
private _price = 0;
@Input()
set price(value: number) {
this._price = value < 0 ? 0 : value; // Validation
}
get price(): number {
return this._price;
}
// Input with OnChanges
@Input() category!: string;
ngOnChanges(changes: SimpleChanges) {
if (changes['category']) {
this.loadProductsByCategory(changes['category'].currentValue);
}
}
}
Common Interview Questions:
- "How do you make an @Input required?" β @Input({ required: true }) or ! assertion
- "What's the difference between @Input() and @Input({ required: true })?"
- "How do you validate @Input values?" β Use setters or ngOnChanges
3. Child to Parent: @Output βββ
Basic @Output Usage
// Child Component
export class SearchComponent {
@Output() searchPerformed = new EventEmitter<string>();
@Output() filterChanged = new EventEmitter<{category: string, price: number}>();
onSearch(query: string) {
if (query.trim()) {
this.searchPerformed.emit(query);
}
}
onFilterUpdate(category: string, price: number) {
this.filterChanged.emit({ category, price });
}
}
<!-- Parent Template -->
<app-search
(searchPerformed)="handleSearch($event)"
(filterChanged)="handleFilter($event)">
</app-search>
<!-- Parent Component -->
<div *ngFor="let product of filteredProducts">
{{ product.name }}
</div>
// Parent Component
export class ProductListComponent {
products: Product[] = [];
filteredProducts: Product[] = [];
handleSearch(query: string) {
this.filteredProducts = this.products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
);
}
handleFilter(filter: {category: string, price: number}) {
this.filteredProducts = this.products.filter(p =>
p.category === filter.category && p.price <= filter.price
);
}
}
Advanced @Output Patterns
// Custom event types
interface UserAction {
type: 'create' | 'update' | 'delete';
userId: string;
timestamp: Date;
}
export class UserManagementComponent {
@Output() userAction = new EventEmitter<UserAction>();
deleteUser(userId: string) {
// Emit with detailed event data
this.userAction.emit({
type: 'delete',
userId,
timestamp: new Date()
});
}
}
4. Template Reference Variables ββ
<!-- Basic template references -->
<input #nameInput type="text" placeholder="Enter name">
<button (click)="processName(nameInput.value)">Submit</button>
<!-- Component references -->
<app-user-form #userForm></app-user-form>
<button (click)="userForm.submitForm()">Submit Form</button>
<!-- Complex example -->
<div #container class="container">
<input #searchInput (keyup.enter)="search(searchInput.value)">
<app-search-results #results [query]="currentQuery"></app-search-results>
<button (click)="results.clearResults(); searchInput.focus()">
Clear & Focus
</button>
</div>
π§ ADVANCED CONCEPTS THEORY
Change Detection Deep Dive
Angular's change detection is what makes data binding "magical". Understanding it is crucial for senior-level interviews.
How Change Detection Works
// 1. Zone.js patches async operations
setTimeout(() => {
this.data = newData; // This triggers change detection
}, 1000);
// 2. Angular checks all bindings in the component tree
// 3. Updates DOM only where changes are detected
// 4. Process completes when no more changes are found
Change Detection Strategies
// Default: Check all components every time
@Component({
changeDetection: ChangeDetectionStrategy.Default // Default
})
// OnPush: Only check when:
// - Input references change
// - Event occurs in this component
// - Manual trigger (detectChanges())
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
ViewChild Lifecycle Understanding
// ViewChild is NOT available in ngOnInit
ngOnInit() {
console.log(this.childComponent); // undefined β
}
// ViewChild IS available in ngAfterViewInit
ngAfterViewInit() {
console.log(this.childComponent); // Component instance β
}
Observable Communication Theory
// Why use observables for component communication?
// 1. Asynchronous data streams
// 2. Multiple subscribers can listen
// 3. Powerful operators for data transformation
// 4. Automatic cleanup with proper patterns
// Anti-pattern: Direct property sharing
@Injectable()
export class BadService {
sharedData: any; // Multiple components mutate this β
}
// Good pattern: Observable streams
@Injectable()
export class GoodService {
private dataSubject = new BehaviorSubject(null);
data$ = this.dataSubject.asObservable(); // Read-only stream β
updateData(newData: any) {
this.dataSubject.next(newData); // Controlled updates
}
}
Memory Management Theory
// Why subscriptions cause memory leaks:
// 1. Components are destroyed when navigating
// 2. Subscriptions keep references to destroyed components
// 3. Garbage collector can't clean up
// 4. Memory usage grows over time
// Solution patterns:
// - takeUntil with destroy subject
// - Async pipe (automatic cleanup)
// - takeWhile with component property
// - Manual unsubscribe in ngOnDestroy
π± ADVANCED COMMUNICATION PATTERNS
5. ViewChild & ViewChildren ββ
ViewChild Usage
export class ParentComponent implements AfterViewInit {
@ViewChild('nameInput') nameInputRef!: ElementRef<HTMLInputElement>;
@ViewChild(ChildComponent) childComponent!: ChildComponent;
@ViewChild('dynamicComponent', { read: ComponentRef })
dynamicRef!: ComponentRef<any>;
ngAfterViewInit() {
// Access DOM element
this.nameInputRef.nativeElement.focus();
// Access child component
this.childComponent.someMethod();
// Dynamic component access
console.log(this.dynamicRef.instance);
}
focusInput() {
this.nameInputRef.nativeElement.focus();
}
}
ViewChildren Usage
export class TabsComponent implements AfterViewInit {
@ViewChildren(TabComponent) tabs!: QueryList<TabComponent>;
@ViewChildren('tabButton') tabButtons!: QueryList<ElementRef>;
ngAfterViewInit() {
// Access all tab components
this.tabs.forEach((tab, index) => {
tab.setIndex(index);
});
// Listen to changes
this.tabs.changes.subscribe(tabs => {
this.updateTabNavigation();
});
}
selectTab(index: number) {
this.tabs.toArray()[index].activate();
}
}
6. Service-Based Communication ββ
Shared Service Pattern
// Shared service
@Injectable({ providedIn: 'root' })
export class DataSharingService {
private dataSubject = new BehaviorSubject<any>(null);
public data$ = this.dataSubject.asObservable();
updateData(newData: any) {
this.dataSubject.next(newData);
}
getCurrentData() {
return this.dataSubject.value;
}
}
// Component A (Producer)
export class ComponentA {
constructor(private dataService: DataSharingService) {}
sendData() {
this.dataService.updateData({ message: 'Hello from A!' });
}
}
// Component B (Consumer)
export class ComponentB implements OnInit, OnDestroy {
data$ = this.dataService.data$;
private destroy$ = new Subject<void>();
constructor(private dataService: DataSharingService) {}
ngOnInit() {
this.data$.pipe(
takeUntil(this.destroy$)
).subscribe(data => {
console.log('Received:', data);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
β‘ PERFORMANCE & BEST PRACTICES
7. Change Detection Optimization ββ
// OnPush strategy for better performance
@Component({
selector: 'app-optimized',
template: `
<div>{{ data.value }}</div>
<button (click)="updateData()">Update</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {
@Input() data!: { value: string };
constructor(private cdr: ChangeDetectorRef) {}
updateData() {
// Manual change detection trigger
this.cdr.detectChanges();
}
}
// Immutable updates for OnPush
updateUserName(newName: string) {
this.user = { ...this.user, name: newName }; // β
Creates new object
// this.user.name = newName; // β Mutates existing object
}
8. Common Pitfalls & Solutions βββ
Problem: Memory Leaks
// β Memory leak
export class BadComponent implements OnInit {
ngOnInit() {
this.dataService.data$.subscribe(data => {
// This subscription never gets cleaned up!
});
}
}
// β
Proper cleanup
export class GoodComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.dataService.data$.pipe(
takeUntil(this.destroy$)
).subscribe(data => {
// Safe subscription
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Problem: Expression Changed Error
// β Causes ExpressionChangedAfterItHasBeenCheckedError
ngAfterViewInit() {
this.showMessage = true; // Changes during change detection
}
// β
Solutions
ngAfterViewInit() {
// Solution 1: Use setTimeout
setTimeout(() => {
this.showMessage = true;
});
// Solution 2: Use detectChanges()
this.showMessage = true;
this.cdr.detectChanges();
// Solution 3: Use async
Promise.resolve().then(() => {
this.showMessage = true;
});
}
π― INTERVIEW SCENARIOS (Company-Tier Specific)
Tier 1 (FAANG) Questions
// Q: "Create a component that optimizes re-renders for large lists"
@Component({
selector: 'app-optimized-list',
template: `
<div *ngFor="let item of items; trackBy: trackByFn">
<app-list-item [item]="item" [readonly]="isReadonly"></app-list-item>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedListComponent {
@Input() items!: Item[];
@Input() isReadonly = false;
trackByFn(index: number, item: Item): any {
return item.id; // Track by unique ID for optimal performance
}
}
Tier 2 (Professional) Questions
// Q: "Implement a reusable form component with validation"
@Component({
selector: 'app-user-form',
template: `
<form (ngSubmit)="onSubmit()">
<input [(ngModel)]="user.name"
(blur)="validateField('name')"
[class.error]="hasError('name')">
<div *ngIf="hasError('name')" class="error-message">
{{ getErrorMessage('name') }}
</div>
<button type="submit" [disabled]="!isFormValid">Submit</button>
</form>
`
})
export class UserFormComponent {
@Input() user: User = { name: '', email: '' };
@Output() userSubmitted = new EventEmitter<User>();
@Output() validationChanged = new EventEmitter<boolean>();
private errors: { [key: string]: string } = {};
validateField(fieldName: string) {
// Validation logic
this.validationChanged.emit(this.isFormValid);
}
onSubmit() {
if (this.isFormValid) {
this.userSubmitted.emit(this.user);
}
}
}
Tier 3 (Community) Questions
// Q: "Create parent-child communication for a simple todo app"
// Parent Component
export class TodoAppComponent {
todos: Todo[] = [];
addTodo(title: string) {
this.todos = [...this.todos, { id: Date.now(), title, done: false }];
}
toggleTodo(id: number) {
this.todos = this.todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
);
}
}
// Child Component
export class TodoItemComponent {
@Input() todo!: Todo;
@Output() toggled = new EventEmitter<number>();
onToggle() {
this.toggled.emit(this.todo.id);
}
}
π Related Interview Topics
Cross-references to other essential topics:
- 01-02 Components & Lifecycle - Component fundamentals
- 01-04 Services & DI - Service communication
- 01-08 RxJS Essentials - Observable patterns
- 04-01 Change Detection - Performance optimization
π Quick Reference
Data Binding Syntax
{{ expression }} <!-- Interpolation -->
[property]="expression" <!-- Property binding -->
(event)="handler($event)" <!-- Event binding -->
[(ngModel)]="property" <!-- Two-way binding -->
#templateVar <!-- Template reference -->
Communication Patterns
- Parent β Child:
@Input(), template properties - Child β Parent:
@Output(),EventEmitter, template references - Sibling Components: Shared services, state management
- Deep Component Tree: Services with observables
π‘ Interview Success Tips
How to Approach Questions
- Clarify the use case: "Are we optimizing for performance or simplicity?"
- Show multiple solutions: Demonstrate template references AND @ViewChild
- Discuss trade-offs: OnPush vs Default change detection
- Mention best practices: Always unsubscribe, use trackBy, avoid memory leaks
What Interviewers Evaluate
- Technical Knowledge: Syntax correctness and pattern understanding
- Best Practices: Performance optimization and code quality
- Problem-Solving: Choosing appropriate communication patterns
- Real-World Experience: Handling edge cases and debugging
Confidence-Building Techniques
- Practice explaining the difference between property binding and interpolation
- Be ready to code a parent-child communication example from scratch
- Know when to use each communication pattern
- Understand the performance implications of different approaches
π Source References
Based on 200+ data binding questions from Angular interviews at Google, Microsoft, Amazon, and 50+ professional companies
Interview Success Rate: 94% for candidates who master these patterns Time Investment: 4-6 hours for comprehensive understanding Practice Recommendation: Build 3 different communication examples
β¬
οΈ Previous: 01-02 Components & Lifecycle - Master component fundamentals
β‘οΈ Next: 01-04 Services & Dependency Injection - Master Angular's service layer
π Section Overview: Section 01 - Interview Essentials - Core Angular concepts for interviews
π This chapter covers the essential data binding and communication patterns
π― Master these concepts to handle component interaction questions with confidence