Advanced Angular Interview Questions with Code Examples
Component Communication & State Management
1. Parent-Child Communication with Input/Output and Service
Parent Component
// parent.component.ts
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-parent',
template: `
<div class="parent">
<h2>Parent Component</h2>
<div>
<input [(ngModel)]="parentMessage" placeholder="Message for child">
<button (click)="sendMessage()">Send via Service</button>
<button (click)="updateDirectMessage()">Send via @Input</button>
</div>
<app-child
[directMessage]="directMessage"
(messageEvent)="receiveMessage($event)">
</app-child>
<div *ngIf="childMessage" class="message-box">
<p><strong>Message from Child:</strong> {{ childMessage }}</p>
</div>
</div>
`,
styles: [`
.parent { padding: 20px; border: 2px solid blue; margin: 10px; }
.message-box { margin-top: 20px; padding: 10px; background-color: #f0f0f0; }
`]
})
export class ParentComponent {
parentMessage = '';
directMessage = '';
childMessage = '';
constructor(private dataService: DataService) {
// Subscribe to messages from the child via service
this.dataService.childMessage$.subscribe(message => {
this.childMessage = message;
});
}
sendMessage() {
this.dataService.sendToChild(this.parentMessage);
}
updateDirectMessage() {
this.directMessage = this.parentMessage;
}
receiveMessage(message: string) {
this.childMessage = message;
}
}
Child Component
// child.component.ts
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-child',
template: `
<div class="child">
<h3>Child Component</h3>
<div *ngIf="directMessage" class="message-box">
<p><strong>Direct Message:</strong> {{ directMessage }}</p>
</div>
<div *ngIf="serviceMessage" class="message-box">
<p><strong>Service Message:</strong> {{ serviceMessage }}</p>
</div>
<div>
<input [(ngModel)]="childReply" placeholder="Reply to parent">
<button (click)="sendViaOutput()">Reply via @Output</button>
<button (click)="sendViaService()">Reply via Service</button>
</div>
</div>
`,
styles: [`
.child { padding: 15px; border: 2px solid green; margin: 10px; }
.message-box { margin: 10px 0; padding: 10px; background-color: #e0f0e0; }
`]
})
export class ChildComponent implements OnInit {
@Input() directMessage = '';
@Output() messageEvent = new EventEmitter<string>();
serviceMessage = '';
childReply = '';
constructor(private dataService: DataService) {}
ngOnInit() {
// Subscribe to messages from the parent via service
this.dataService.parentMessage$.subscribe(message => {
this.serviceMessage = message;
});
}
sendViaOutput() {
this.messageEvent.emit(this.childReply);
}
sendViaService() {
this.dataService.sendToParent(this.childReply);
}
}
Shared Service
// data.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
// Subjects for bi-directional communication
private parentMessageSource = new Subject<string>();
private childMessageSource = new Subject<string>();
// Observable streams
parentMessage$ = this.parentMessageSource.asObservable();
childMessage$ = this.childMessageSource.asObservable();
// Method for parent to send message to child
sendToChild(message: string) {
this.parentMessageSource.next(message);
}
// Method for child to send message to parent
sendToParent(message: string) {
this.childMessageSource.next(message);
}
}
2. Custom State Management with Observables
// state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
// Interface for our application state
export interface AppState {
user: {
id: number;
name: string;
isAuthenticated: boolean;
};
todos: Todo[];
uiState: {
isLoading: boolean;
darkMode: boolean;
sidebarOpen: boolean;
};
}
export interface Todo {
id: number;
text: string;
completed: boolean;
}
// Initial state
const initialState: AppState = {
user: {
id: 0,
name: '',
isAuthenticated: false
},
todos: [],
uiState: {
isLoading: false,
darkMode: false,
sidebarOpen: true
}
};
@Injectable({
providedIn: 'root'
})
export class StateService {
// BehaviorSubject containing the state
private state$ = new BehaviorSubject<AppState>(initialState);
// Method to get the current state
getState(): Observable<AppState> {
return this.state$.asObservable();
}
// Method to get a specific slice of state
select<T>(selector: (state: AppState) => T): Observable<T> {
return this.state$.pipe(
map(state => selector(state))
);
}
// Update state method (immutable pattern)
setState(stateFn: (state: AppState) => AppState): void {
const currentState = this.state$.getValue();
const newState = stateFn(currentState);
this.state$.next(newState);
}
// User actions
login(id: number, name: string): void {
this.setState(state => ({
...state,
user: {
id,
name,
isAuthenticated: true
}
}));
}
logout(): void {
this.setState(state => ({
...state,
user: {
id: 0,
name: '',
isAuthenticated: false
}
}));
}
// Todo actions
addTodo(text: string): void {
const newTodo: Todo = {
id: Date.now(),
text,
completed: false
};
this.setState(state => ({
...state,
todos: [...state.todos, newTodo]
}));
}
toggleTodo(id: number): void {
this.setState(state => ({
...state,
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
}));
}
removeTodo(id: number): void {
this.setState(state => ({
...state,
todos: state.todos.filter(todo => todo.id !== id)
}));
}
// UI state actions
setLoading(isLoading: boolean): void {
this.setState(state => ({
...state,
uiState: {
...state.uiState,
isLoading
}
}));
}
toggleDarkMode(): void {
this.setState(state => ({
...state,
uiState: {
...state.uiState,
darkMode: !state.uiState.darkMode
}
}));
}
toggleSidebar(): void {
this.setState(state => ({
...state,
uiState: {
...state.uiState,
sidebarOpen: !state.uiState.sidebarOpen
}
}));
}
}
Usage in Component
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { StateService, Todo } from './state.service';
@Component({
selector: 'app-root',
template: `
<div [class.dark-mode]="darkMode$ | async">
<h1>Todo App</h1>
<div *ngIf="(isAuthenticated$ | async); else loginBlock">
<h2>Welcome, {{ userName$ | async }}!</h2>
<button (click)="logout()">Logout</button>
<div class="todo-form">
<input [(ngModel)]="newTodo" placeholder="Add a new task...">
<button (click)="addTodo()">Add</button>
</div>
<div *ngIf="(isLoading$ | async)" class="loader">Loading...</div>
<ul class="todo-list">
<li *ngFor="let todo of todos$ | async" [class.completed]="todo.completed">
<span (click)="toggleTodo(todo.id)">{{ todo.text }}</span>
<button (click)="removeTodo(todo.id)">✕</button>
</li>
</ul>
</div>
<ng-template #loginBlock>
<div class="login-form">
<h2>Please Login</h2>
<button (click)="login()">Login as Demo User</button>
</div>
</ng-template>
<div class="settings">
<button (click)="toggleDarkMode()">Toggle Dark Mode</button>
<button (click)="toggleSidebar()">Toggle Sidebar</button>
</div>
</div>
`,
styles: [`
.dark-mode { background-color: #333; color: white; }
.completed { text-decoration: line-through; color: #888; }
.loader { margin: 10px 0; color: orange; }
`]
})
export class AppComponent implements OnInit {
newTodo = '';
// Selectors for specific state slices
todos$: Observable<Todo[]>;
isAuthenticated$: Observable<boolean>;
userName$: Observable<string>;
isLoading$: Observable<boolean>;
darkMode$: Observable<boolean>;
constructor(private stateService: StateService) {
// Select specific slices of state
this.todos$ = this.stateService.select(state => state.todos);
this.isAuthenticated$ = this.stateService.select(state => state.user.isAuthenticated);
this.userName$ = this.stateService.select(state => state.user.name);
this.isLoading$ = this.stateService.select(state => state.uiState.isLoading);
this.darkMode$ = this.stateService.select(state => state.uiState.darkMode);
}
ngOnInit() {
// Example of showing loading state
this.stateService.setLoading(true);
setTimeout(() => {
this.stateService.setLoading(false);
}, 1000);
}
login() {
this.stateService.login(1, 'Demo User');
}
logout() {
this.stateService.logout();
}
addTodo() {
if (this.newTodo.trim()) {
this.stateService.addTodo(this.newTodo);
this.newTodo = '';
}
}
toggleTodo(id: number) {
this.stateService.toggleTodo(id);
}
removeTodo(id: number) {
this.stateService.removeTodo(id);
}
toggleDarkMode() {
this.stateService.toggleDarkMode();
}
toggleSidebar() {
this.stateService.toggleSidebar();
}
}
3. Reactive Form with Complex Validation
// registration-form.component.ts
import { Component, OnInit } from '@angular/core';
import {
FormBuilder,
FormGroup,
Validators,
AbstractControl,
ValidationErrors,
AsyncValidatorFn
} from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, debounceTime, switchMap, catchError, first } from 'rxjs/operators';
import { UserService } from './user.service';
@Component({
selector: 'app-registration-form',
template: `
<div class="form-container">
<h2>Registration Form</h2>
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="email">Email</label>
<input id="email" type="email" formControlName="email">
<div *ngIf="email?.invalid && (email?.dirty || email?.touched)" class="error">
<div *ngIf="email?.errors?.['required']">Email is required</div>
<div *ngIf="email?.errors?.['email']">Please enter a valid email</div>
<div *ngIf="email?.errors?.['emailTaken']">This email is already registered</div>
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" type="password" formControlName="password">
<div *ngIf="password?.invalid && (password?.dirty || password?.touched)" class="error">
<div *ngIf="password?.errors?.['required']">Password is required</div>
<div *ngIf="password?.errors?.['minlength']">Password must be at least 8 characters</div>
<div *ngIf="password?.errors?.['passwordStrength']">
Password must contain at least one uppercase letter, one lowercase letter,
one number, and one special character
</div>
</div>
</div>
<div class="form-group">
<label for="confirmPassword">Confirm Password</label>
<input id="confirmPassword" type="password" formControlName="confirmPassword">
<div *ngIf="confirmPassword?.invalid && (confirmPassword?.dirty || confirmPassword?.touched)" class="error">
<div *ngIf="confirmPassword?.errors?.['required']">Confirm password is required</div>
<div *ngIf="registrationForm.errors?.['passwordMismatch']">Passwords don't match</div>
</div>
</div>
<div formGroupName="personalInfo">
<div class="form-group">
<label for="firstName">First Name</label>
<input id="firstName" type="text" formControlName="firstName">
<div *ngIf="firstName?.invalid && (firstName?.dirty || firstName?.touched)" class="error">
<div *ngIf="firstName?.errors?.['required']">First name is required</div>
</div>
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input id="lastName" type="text" formControlName="lastName">
<div *ngIf="lastName?.invalid && (lastName?.dirty || lastName?.touched)" class="error">
<div *ngIf="lastName?.errors?.['required']">Last name is required</div>
</div>
</div>
<div class="form-group">
<label for="age">Age</label>
<input id="age" type="number" formControlName="age">
<div *ngIf="age?.invalid && (age?.dirty || age?.touched)" class="error">
<div *ngIf="age?.errors?.['required']">Age is required</div>
<div *ngIf="age?.errors?.['min']">Age must be at least 18</div>
<div *ngIf="age?.errors?.['max']">Age must not exceed 120</div>
</div>
</div>
</div>
<div class="form-group checkbox">
<input id="terms" type="checkbox" formControlName="terms">
<label for="terms">I agree to the Terms and Conditions</label>
<div *ngIf="terms?.invalid && (terms?.dirty || terms?.touched)" class="error">
<div *ngIf="terms?.errors?.['required']">You must agree to the terms</div>
</div>
</div>
<button type="submit" [disabled]="registrationForm.invalid || registrationForm.pending">
<span *ngIf="registrationForm.pending">Validating...</span>
<span *ngIf="!registrationForm.pending">Register</span>
</button>
</form>
<div *ngIf="submitted">
<h3>Registration Successful!</h3>
<pre>{{ formValue | json }}</pre>
</div>
</div>
`,
styles: [`
.form-container { max-width: 500px; margin: 0 auto; padding: 20px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; }
.form-group input { width: 100%; padding: 8px; border: 1px solid #ddd; }
.checkbox { display: flex; align-items: center; gap: 10px; }
.checkbox input { width: auto; }
.error { color: red; font-size: 12px; margin-top: 5px; }
button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
button:disabled { background-color: #cccccc; cursor: not-allowed; }
`]
})
export class RegistrationFormComponent implements OnInit {
registrationForm: FormGroup;
submitted = false;
formValue: any;
constructor(
private fb: FormBuilder,
private userService: UserService
) {
this.registrationForm = this.fb.group({
email: ['', {
validators: [Validators.required, Validators.email],
asyncValidators: [this.emailExistsValidator()]
}],
password: ['', [
Validators.required,
Validators.minLength(8),
this.passwordStrengthValidator()
]],
confirmPassword: ['', Validators.required],
personalInfo: this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
age: ['', [Validators.required, Validators.min(18), Validators.max(120)]]
}),
terms: [false, Validators.requiredTrue]
}, { validators: this.passwordMatchValidator });
}
ngOnInit() {
// Example of value changes subscription
this.registrationForm.valueChanges.subscribe(value => {
console.log('Form value changed:', value);
});
}
// Password strength custom validator
passwordStrengthValidator() {
return (control: AbstractControl): ValidationErrors | null => {
const value: string = control.value || '';
const hasUpperCase = /[A-Z]/.test(value);
const hasLowerCase = /[a-z]/.test(value);
const hasNumeric = /[0-9]/.test(value);
const hasSpecialChar = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(value);
const valid = hasUpperCase && hasLowerCase && hasNumeric && hasSpecialChar;
return !valid ? { passwordStrength: true } : null;
};
}
// Cross-field validator for password match
passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
const password = group.get('password')?.value;
const confirmPassword = group.get('confirmPassword')?.value;
return password === confirmPassword ? null : { passwordMismatch: true };
}
// Async validator for email uniqueness
emailExistsValidator(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
const email = control.value;
if (!email) {
return of(null);
}
return of(email).pipe(
debounceTime(400),
switchMap(email =>
this.userService.checkEmailExists(email).pipe(
map(exists => exists ? { emailTaken: true } : null),
catchError(() => of(null))
)
),
first() // Complete after first emission
);
};
}
// Getter methods for form controls (to simplify template code)
get email() { return this.registrationForm.get('email'); }
get password() { return this.registrationForm.get('password'); }
get confirmPassword() { return this.registrationForm.get('confirmPassword'); }
get firstName() { return this.registrationForm.get('personalInfo.firstName'); }
get lastName() { return this.registrationForm.get('personalInfo.lastName'); }
get age() { return this.registrationForm.get('personalInfo.age'); }
get terms() { return this.registrationForm.get('terms'); }
onSubmit() {
if (this.registrationForm.valid) {
this.submitted = true;
this.formValue = this.registrationForm.value;
// In a real app, you would call a service to submit the form
console.log('Form submitted successfully', this.formValue);
} else {
// Mark all fields as touched to trigger validation messages
this.markFormGroupTouched(this.registrationForm);
}
}
// Helper method to mark all controls in a form group as touched
markFormGroupTouched(formGroup: FormGroup) {
Object.values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control instanceof FormGroup) {
this.markFormGroupTouched(control);
}
});
}
}
// Mock UserService for async validation
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class UserService {
private existingEmails = ['test@example.com', 'admin@example.com', 'user@example.com'];
checkEmailExists(email: string): Observable<boolean> {
// Simulate API call with delay
return of(this.existingEmails.includes(email)).pipe(delay(1000));
}
}
Performance Optimization
4. OnPush Change Detection Strategy
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { ParentComponent } from './parent.component';
import { ChildComponent } from './child.component';
@NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent, ParentComponent, ChildComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
// parent.component.ts
import { Component } from '@angular/core';
export interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-parent',
template: `
<div class="parent-container">
<h2>Parent Component (Default Change Detection)</h2>
<p>Counter: {{ counter }}</p>
<p>Last Update: {{ lastUpdate }}</p>
<div class="controls">
<button (click)="incrementCounter()">Increment Counter</button>
<button (click)="updateUserObject()">Update User Object</button>
<button (click)="updateUserProperty()">Update User Property</button>
<button (click)="pushToArray()">Add Item to Array</button>
<button (click)="updateArrayReference()">Create New Array</button>
</div>
<div class="user-input">
<input [(ngModel)]="newName" placeholder="New user name">
<button (click)="updateName()">Update Name</button>
</div>
<div class="child-components">
<app-child
[counter]="counter"
[user]="user"
[items]="items"
(update)="onChildUpdate($event)">
</app-child>
</div>
</div>
`,
styles: [`
.parent-container { padding: 20px; border: 2px solid blue; margin: 10px; }
.controls { margin: 15px 0; display: flex; gap: 10px; }
.user-input { margin: 15px 0; }
.child-components { margin-top: 20px; }
`]
})
export class ParentComponent {
counter = 0;
lastUpdate = new Date().toLocaleTimeString();
user: User = { id: 1, name: 'John Doe', email: 'john@example.com' };
items: string[] = ['Item 1', 'Item 2', 'Item 3'];
newName = '';
incrementCounter() {
this.counter++;
this.updateTimestamp();
}
updateUserObject() {
// Creates a new reference (triggers OnPush detection)
this.user = { ...this.user };
this.updateTimestamp();
}
updateUserProperty() {
// Mutates the existing object (doesn't trigger OnPush unless marked)
this.user.name = 'Jane Doe';
this.updateTimestamp();
}
pushToArray() {
// Mutates the array (doesn't trigger OnPush unless marked)
this.items.push(`Item ${this.items.length + 1}`);
this.updateTimestamp();
}
updateArrayReference() {
// Creates a new reference (triggers OnPush detection)
this.items = [...this.items, `Item ${this.items.length + 1}`];
this.updateTimestamp();
}
updateName() {
if (this.newName) {
// Creates a new reference (triggers OnPush detection)
this.user = { ...this.user, name: this.newName };
this.newName = '';
this.updateTimestamp();
}
}
onChildUpdate(message: string) {
console.log('Message from child:', message);
this.updateTimestamp();
}
updateTimestamp() {
this.lastUpdate = new Date().toLocaleTimeString();
}
}
// child.component.ts
import {
Component,
Input,
Output,
EventEmitter,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnInit,
OnChanges,
SimpleChanges
} from '@angular/core';
import { User } from './parent.component';
@Component({
selector: 'app-child',
template: `
<div class="child-container">
<h3>Child Component (OnPush Strategy)</h3>
<p>Counter from parent: {{ counter }}</p>
<p>User: {{ user.name }} ({{ user.email }})</p>
<p>Items count: {{ items.length }}</p>
<p>Local counter: {{ localCounter }}</p>
<p>Last render: {{ lastRender }}</p>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<div class="controls">
<button (click)="incrementLocal()">Increment Local Counter</button>
<button (click)="emitEvent()">Emit Event</button>
<button (click)="forceDetection()">Force Detection</button>
</div>
</div>
`,
styles: [`
.child-container { padding: 15px; border: 2px solid green; margin: 10px; }
.controls { margin-top: 15px; display: flex; gap: 10px; }
`],
// Use OnPush change detection strategy
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit, OnChanges {
@Input() counter = 0;
@Input() user!: User;
@Input() items!: string[];
@Output() update = new EventEmitter<string>();
localCounter = 0;
lastRender = new Date().toLocaleTimeString();
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
// Example of marking for check after an async operation
setTimeout(() => {
this.localCounter = 100;
// Need to manually trigger change detection since we're using OnPush
this.cd.markForCheck();
}, 3000);
}
ngOnChanges(changes: SimpleChanges) {
console.log('Child received changes:', changes);
this.updateTimestamp();
// Log which input triggered change detection
if (changes['counter']) {
console.log('Counter changed to:', this.counter);
}
if (changes['user']) {
console.log('User reference changed:', this.user);
// Check if it's a reference change or initial change
if (!changes['user'].firstChange) {
console.log('Previous user:', changes['user'].previousValue);
console.log('Current user:', changes['user'].currentValue);
}
}
if (changes['items']) {
console.log('Items array reference changed:', this.items);
}
}
incrementLocal() {
this.localCounter++;
this.updateTimestamp();
// Need to manually trigger change detection since we're using OnPush
this.cd.markForCheck();
}
emitEvent() {
this.update.emit(`Event from child at ${new Date().toLocaleTimeString()}`);
// No need to call markForCheck() here since Angular automatically does this
// when an @Output event is emitted
}
forceDetection() {
this.updateTimestamp();
// Force change detection
this.cd.detectChanges();
}
updateTimestamp() {
this.lastRender = new Date().toLocaleTimeString();
}
}
5. Virtual Scrolling
// app.module.ts
import { NgModule } from '@angular/core