Skip to main content
Interview Questions

Interview Questions Overview

Estimated Time: 4 hours | Difficulty: All Levels | Prerequisites: Complete Angular Course
This comprehensive guide covers Angular interview questions from basic to advanced levels, helping you prepare for technical interviews at any stage of your career.

Core Angular Concepts

Q1: What is Angular and how does it differ from AngularJS?

Angular is a TypeScript-based platform for building web applications, completely rewritten from AngularJS.Key differences:
  • Architecture: Angular uses component-based architecture; AngularJS uses MVC
  • Language: Angular uses TypeScript; AngularJS uses JavaScript
  • Mobile: Angular supports mobile development; AngularJS doesn’t
  • Performance: Angular has improved change detection and AOT compilation
  • CLI: Angular has powerful CLI; AngularJS doesn’t have official CLI

Q2: Explain the Angular component lifecycle hooks

@Component({...})
export class ExampleComponent implements OnInit, OnChanges, OnDestroy {
  // Called once before ngOnInit when inputs change
  ngOnChanges(changes: SimpleChanges) {}
  
  // Called once after first ngOnChanges
  ngOnInit() {}
  
  // Called every change detection cycle
  ngDoCheck() {}
  
  // Called once after first ngDoCheck
  ngAfterContentInit() {}
  
  // Called after every ngDoCheck
  ngAfterContentChecked() {}
  
  // Called once after first ngAfterContentChecked
  ngAfterViewInit() {}
  
  // Called after every ngAfterContentChecked
  ngAfterViewChecked() {}
  
  // Called before component is destroyed
  ngOnDestroy() {}
}
Order: constructor → ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit → ngAfterContentChecked → ngAfterViewInit → ngAfterViewChecked → ngOnDestroy

Q3: What are Angular Signals?

Signals are a reactive primitive introduced in Angular 16 for fine-grained reactivity.
// Creating signals
const count = signal(0);
const name = signal('Angular');

// Reading signals
console.log(count()); // 0

// Updating signals
count.set(5);
count.update(v => v + 1);

// Computed signals (derived values)
const doubled = computed(() => count() * 2);

// Effects (side effects)
effect(() => {
  console.log(`Count is: ${count()}`);
});
Benefits:
  • Fine-grained reactivity (only affected components update)
  • Better performance than Zone.js change detection
  • Simpler mental model than RxJS for state
  • Lazy evaluation of computed values

Q4: Explain the difference between OnPush and Default change detection

Default Strategy: Angular checks the entire component tree on every change detection cycle.OnPush Strategy: Component only checked when:
  1. Input reference changes
  2. Event from component or child
  3. Async pipe emits new value
  4. Manual markForCheck() or detectChanges()
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {
  data = input<Data>(); // Only triggers on reference change
  
  // Manual trigger if needed
  private cdr = inject(ChangeDetectorRef);
  
  forceUpdate() {
    this.cdr.markForCheck();
  }
}
Best Practice: Use OnPush everywhere with immutable data patterns.

Dependency Injection

Q5: What is Dependency Injection and how does Angular implement it?

DI is a design pattern where dependencies are provided to a class rather than created by it.
// Service definition
@Injectable({ providedIn: 'root' })
export class UserService {
  getUser(id: string): Observable<User> { ... }
}

// Injection in component
@Component({...})
export class UserComponent {
  private userService = inject(UserService);
  
  // Or via constructor
  constructor(private userService: UserService) {}
}
Hierarchical Injector:
  • Root level: providedIn: 'root' (singleton)
  • Module level: providers array in NgModule
  • Component level: providers in component (new instance per component)
Benefits: Loose coupling, testability, reusability

Q6: Explain providedIn options

// Singleton for entire app
@Injectable({ providedIn: 'root' })

// Singleton per lazy-loaded module
@Injectable({ providedIn: 'any' })

// Provided to specific module
@Injectable({ providedIn: SomeModule })

// Platform level (shared across apps)
@Injectable({ providedIn: 'platform' })

// Not automatically provided (must add to providers array)
@Injectable()

Templates & Data Binding

Q7: Explain the different types of data binding in Angular

<!-- 1. Interpolation (one-way) -->
<p>{{ message }}</p>

<!-- 2. Property Binding (one-way) -->
<img [src]="imageUrl">
<button [disabled]="isDisabled">

<!-- 3. Event Binding (one-way) -->
<button (click)="onClick()">

<!-- 4. Two-way Binding -->
<input [(ngModel)]="name">
<!-- Equivalent to: -->
<input [ngModel]="name" (ngModelChange)="name = $event">

<!-- 5. Attribute Binding -->
<td [attr.colspan]="colSpan">

<!-- 6. Class Binding -->
<div [class.active]="isActive">

<!-- 7. Style Binding -->
<div [style.color]="textColor">

Q8: What is the difference between ng-template, ng-container, and ng-content?

ng-template: Defines a template that is not rendered by default
<ng-template #myTemplate let-name="name">
  <p>Hello, {{ name }}</p>
</ng-template>
<ng-container *ngTemplateOutlet="myTemplate; context: {name: 'Angular'}"/>
ng-container: Logical grouping without adding extra DOM elements
<ng-container *ngIf="condition">
  <p>No wrapper element added</p>
</ng-container>
ng-content: Content projection (transclusion)
<!-- parent-component.html -->
<child-component>
  <p>This content is projected</p>
</child-component>

<!-- child-component.html -->
<div class="wrapper">
  <ng-content></ng-content>
</div>

Routing

Q9: What are Route Guards and their types?

// CanActivate - Control access to route
export const authGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthService);
  return auth.isLoggedIn() ? true : inject(Router).createUrlTree(['/login']);
};

// CanActivateChild - Control access to child routes
export const adminGuard: CanActivateChildFn = () => {
  return inject(AuthService).isAdmin();
};

// CanDeactivate - Prevent leaving route
export const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges> = (component) => {
  return component.hasUnsavedChanges() 
    ? confirm('Discard changes?') 
    : true;
};

// CanMatch - Prevent route matching entirely
export const featureGuard: CanMatchFn = () => {
  return inject(FeatureService).isEnabled('new-feature');
};

// Resolve - Pre-fetch data before activation
export const userResolver: ResolveFn<User> = (route) => {
  return inject(UserService).getUser(route.params['id']);
};

// Routes configuration
const routes: Routes = [
  {
    path: 'admin',
    canActivate: [authGuard],
    canActivateChild: [adminGuard],
    canMatch: [featureGuard],
    children: [...]
  }
];

Q10: Explain lazy loading in Angular

// Lazy load routes
const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('./products/products.routes')
      .then(m => m.PRODUCTS_ROUTES)
  },
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin.component')
      .then(m => m.AdminComponent)
  }
];

// Preloading strategies
@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      preloadingStrategy: PreloadAllModules
      // Or custom: CustomPreloadingStrategy
    })
  ]
})

// Deferrable views (Angular 17+)
@defer (on viewport) {
  <app-heavy-component />
} @placeholder {
  <app-skeleton />
}

RxJS & HTTP

Q11: What is the difference between Subject, BehaviorSubject, and ReplaySubject?

// Subject - No initial value, late subscribers miss past emissions
const subject = new Subject<number>();
subject.next(1);
subject.subscribe(v => console.log(v)); // Won't receive 1
subject.next(2); // Logs: 2

// BehaviorSubject - Has initial/current value
const behavior = new BehaviorSubject<number>(0);
behavior.next(1);
behavior.subscribe(v => console.log(v)); // Logs: 1 (current value)
behavior.getValue(); // Returns current value: 1

// ReplaySubject - Replays N values to new subscribers
const replay = new ReplaySubject<number>(2); // Buffer size 2
replay.next(1);
replay.next(2);
replay.next(3);
replay.subscribe(v => console.log(v)); // Logs: 2, 3

// AsyncSubject - Only emits last value on completion
const async = new AsyncSubject<number>();
async.next(1);
async.next(2);
async.subscribe(v => console.log(v)); // Nothing yet
async.complete(); // Now logs: 2

Q12: Explain common RxJS operators

// Transformation
map(x => x * 2)              // Transform each value
switchMap(x => http.get(x))  // Switch to new observable, cancel previous
mergeMap(x => http.get(x))   // Merge all inner observables
concatMap(x => http.get(x))  // Queue inner observables sequentially

// Filtering
filter(x => x > 0)           // Filter values
distinctUntilChanged()       // Only emit when value changes
debounceTime(300)            // Wait for pause in emissions
throttleTime(300)            // Rate limit emissions
take(5)                      // Take first N values
takeUntil(destroy$)          // Take until signal

// Combination
combineLatest([a$, b$])      // Emit when any source emits
forkJoin([a$, b$])           // Emit when all complete
merge(a$, b$)                // Merge multiple streams
withLatestFrom(other$)       // Combine with latest from another

// Error Handling
catchError(err => of(null))  // Handle errors
retry(3)                     // Retry on error
retryWhen(...)               // Custom retry logic

// Utility
tap(x => console.log(x))     // Side effects
shareReplay(1)               // Share and cache last value

Testing

Q13: How do you test Angular components?

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;
  let userServiceSpy: jasmine.SpyObj<UserService>;
  
  beforeEach(async () => {
    userServiceSpy = jasmine.createSpyObj('UserService', ['getUser']);
    userServiceSpy.getUser.and.returnValue(of({ id: '1', name: 'Test' }));
    
    await TestBed.configureTestingModule({
      imports: [UserComponent],
      providers: [
        { provide: UserService, useValue: userServiceSpy }
      ]
    }).compileComponents();
    
    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
  });
  
  it('should create', () => {
    expect(component).toBeTruthy();
  });
  
  it('should display user name', () => {
    fixture.componentRef.setInput('userId', '1');
    fixture.detectChanges();
    
    const nameElement = fixture.nativeElement.querySelector('.user-name');
    expect(nameElement.textContent).toContain('Test');
  });
  
  it('should call service on init', () => {
    fixture.componentRef.setInput('userId', '1');
    fixture.detectChanges();
    
    expect(userServiceSpy.getUser).toHaveBeenCalledWith('1');
  });
});

Performance

Q14: How do you optimize Angular application performance?

Build-time optimizations:
  • AOT compilation
  • Tree shaking
  • Code splitting / lazy loading
  • Bundle analysis and optimization
Runtime optimizations:
// 1. OnPush change detection
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

// 2. TrackBy for lists
@for (item of items(); track item.id) {
  <app-item [item]="item" />
}

// 3. Signals for reactive state
count = signal(0);
doubled = computed(() => this.count() * 2);

// 4. Deferrable views
@defer (on viewport) {
  <heavy-component />
}

// 5. Virtual scrolling for large lists
<cdk-virtual-scroll-viewport itemSize="50">
  <div *cdkVirtualFor="let item of items">{{ item }}</div>
</cdk-virtual-scroll-viewport>

// 6. Pure pipes
@Pipe({ pure: true })

// 7. Unsubscribe properly
takeUntilDestroyed(this.destroyRef)

Advanced Topics

Q15: What is Zone.js and how does Angular use it?

Zone.js is a library that patches async APIs (setTimeout, Promise, XHR, etc.) to notify Angular when async operations complete.
// Zone.js patches these to trigger change detection:
setTimeout(() => {})
Promise.resolve().then(() => {})
fetch('/api').then(() => {})
element.addEventListener('click', () => {})

// Running outside Angular zone (for performance)
@Component({...})
export class PerformanceComponent {
  private ngZone = inject(NgZone);
  
  heavyOperation() {
    this.ngZone.runOutsideAngular(() => {
      // This won't trigger change detection
      setInterval(() => this.trackMouse(), 10);
    });
  }
  
  updateUI() {
    this.ngZone.run(() => {
      // This triggers change detection
      this.value = 'updated';
    });
  }
}
Zoneless Angular (experimental in v18):
bootstrapApplication(App, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});

Q16: Explain Angular SSR and Hydration

Server-Side Rendering (SSR) renders the application on the server, sending HTML to the client.Hydration reuses the server-rendered DOM instead of re-creating it.
// app.config.server.ts
export const config: ApplicationConfig = {
  providers: [
    provideServerRendering(),
    provideClientHydration()
  ]
};

// Benefits:
// - Better SEO (search engines see content)
// - Faster First Contentful Paint
// - Better Core Web Vitals
// - Works with JavaScript disabled

// Hydration skipping for dynamic content
@Component({
  host: { ngSkipHydration: 'true' }
})
export class DynamicComponent {}

Coding Challenges

@Component({
  template: `
    <input [formControl]="searchControl" placeholder="Search...">
    @for (result of results(); track result.id) {
      <div>{{ result.name }}</div>
    }
  `
})
export class SearchComponent {
  private searchService = inject(SearchService);
  private destroyRef = inject(DestroyRef);
  
  searchControl = new FormControl('');
  results = signal<SearchResult[]>([]);
  
  ngOnInit() {
    this.searchControl.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      filter(term => term.length >= 2),
      switchMap(term => this.searchService.search(term)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(results => this.results.set(results));
  }
}

Challenge 2: Create a reusable modal service

@Injectable({ providedIn: 'root' })
export class ModalService {
  private vcr!: ViewContainerRef;
  
  setContainer(vcr: ViewContainerRef) {
    this.vcr = vcr;
  }
  
  open<T, R>(component: Type<T>, data?: Partial<T>): Observable<R> {
    const subject = new Subject<R>();
    const ref = this.vcr.createComponent(component);
    
    if (data) {
      Object.entries(data).forEach(([key, value]) => {
        ref.setInput(key, value);
      });
    }
    
    // Assuming component has 'closed' output
    ref.instance.closed?.subscribe((result: R) => {
      subject.next(result);
      subject.complete();
      ref.destroy();
    });
    
    return subject.asObservable();
  }
}

Tips for Success

Understand Fundamentals

Know DI, change detection, and component lifecycle deeply

Practice Coding

Be ready to write code on whiteboard or shared editor

Know RxJS

Understand operators and when to use them

Discuss Trade-offs

Show you can evaluate different approaches

Next: Best Practices

Learn Angular best practices and style guide