export class MyComponent implements OnInit, OnChanges, OnDestroy {
@Input() data!: string;
ngOnChanges(changes: SimpleChanges) { /* input changed */ }
ngOnInit() { /* component initialized */ }
ngOnDestroy() { /* cleanup before destroy */ }
}
Hooks are called in a specific order: ngOnChanges first, then ngOnInit, then the check and view hooks. ngOnDestroy is always called last before the component is removed.
Why it matters: Tests fundamental understanding of component initialization flow. Shows you know when and how Angular manages component lifecycle.
Real applications: Every component uses at least ngOnInit and ngOnDestroy. Lifecycle hooks are essential for initialization, data fetching, and resource cleanup.
Common mistakes: Developers don't understand hook order, running initialization code in wrong hooks. They don't clean up subscriptions causing memory leaks. They treat constructor and ngOnInit as equivalent.
export class UserComponent implements OnInit {
@Input() userId!: string;
user: User;
ngOnInit() {
// Safe to use @Input values here
this.user = this.userService.getUser(this.userId);
}
}
ngOnInit runs only once during the component's lifetime. For reacting to subsequent input changes, use ngOnChanges instead.
Why it matters: Tests knowledge of component initialization timing. Shows you understand when @Input values are available for use.
Real applications: Fetching user data by ID, initializing timers, setting up subscriptions all happen in ngOnInit after @Input values are bound.
Common mistakes: Developers use constructor for logic that depends on @Input values. They run initialization in ngOnChanges instead of ngOnInit, causing performance issues.
export class ExampleComponent implements OnInit {
@Input() name!: string;
constructor(private service: DataService) {
// DI only — this.name is undefined here
}
ngOnInit() {
// this.name is available here
this.service.load(this.name);
}
}
The constructor runs before Angular processes any bindings. ngOnInit is the safe place to use @Input values and interact with services.
Why it matters: Tests deep understanding of Angular initialization order and TypeScript patterns. Knowing the difference prevents bugs from undefined @Input values.
Real applications: Constructor handles DI only. ngOnInit loads data for display, subscribes to status changes, initializes timers.
Common mistakes: Developers use @Input values in constructor, getting undefined. They try to call services in constructor that depend on @Input. They don't understand Angular binding timing.
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
console.log('Previous:', changes['userId'].previousValue);
console.log('Current:', changes['userId'].currentValue);
console.log('First change:', changes['userId'].firstChange);
}
}
Each entry in SimpleChanges has previousValue, currentValue, and firstChange properties. The firstChange flag is true during the initial binding.
Why it matters: Tests knowledge of input change detection and reaction patterns. Shows you can build components that respond to parent updates.
Real applications: Reloading data when userId changes, refiltering lists when filter changes, updating chart visualization when config changes.
Common mistakes: Developers don't implement ngOnChanges and miss parent property updates. They don't check firstChange, processing initial values twice. They don't understand ngOnChanges runs before ngOnInit.
ngDoCheck() {
if (this.currentName !== this.previousName) {
this.previousName = this.currentName;
this.changeLog.push('Name changed to: ' + this.currentName);
}
}
ngDoCheck runs on every change detection cycle, even when no inputs have changed. Avoid expensive operations here to prevent performance issues.
Why it matters: Tests understanding of custom change detection and performance implications. Shows you know when Angular's default detection isn't enough.
Real applications: Detecting deep mutations Angular misses like object property changes, tracking complex data transformations, monitoring performance metrics.
Common mistakes: Developers run expensive operations in ngDoCheck, killing performance. They overuse ngDoCheck instead of leveraging OnPush change detection. They don't understand it runs excessively.
@ViewChild('myInput') inputRef!: ElementRef;
ngAfterViewInit() {
this.inputRef.nativeElement.focus();
}
Do not change component data in ngAfterViewInit as it can trigger ExpressionChangedAfterItHasBeenCheckedError. If needed, wrap changes in setTimeout().
Why it matters: Tests understanding of template rendering timing and ViewChild access patterns. Shows you know how to safely interact with the DOM.
Real applications: Focusing input fields for better UX, initializing third-party JS libraries like charts/maps, setting element sizes based on viewport.
Common mistakes: Developers change component state in ngAfterViewInit causing ExpressionChangedAfterItHasBeenCheckError. They try to access @ViewChild in ngOnInit when it's undefined.
@ContentChild(ChildDirective) child!: ChildDirective;
ngAfterContentInit() {
console.log(this.child); // Now available
}
@ContentChild references are only safe to access from this hook onwards. This hook is called once, while ngAfterContentChecked runs on every change detection cycle.
Why it matters: Tests knowledge of content projection patterns and component composition. Shows you understand how Angular handles ng-content.
Real applications: Wrapper components accessing projected children, custom form components reading child form controls, layout components managing projected content.
Common mistakes: Developers try to access @ContentChild before ngAfterContentInit, getting undefined. They confuse @ContentChild with @ViewChild. They don't understand projection timing.
private sub!: Subscription;
ngOnInit() {
this.sub = this.data$.subscribe(val => this.value = val);
}
ngOnDestroy() {
this.sub.unsubscribe(); // Prevent memory leaks
}
In Angular 16+, use takeUntilDestroyed() to auto-unsubscribe instead of manually implementing ngOnDestroy. Always clean up event listeners and intervals here.
Why it matters: Tests understanding of resource cleanup and memory leak prevention. Shows you can prevent common memory leak issues.
Real applications: Unsubscribing from data observables, removing window resize listeners, clearing polling timers, releasing external resources.
Common mistakes: Developers never call ngOnDestroy, leaking subscriptions and memory. They don't understand subscriptions accumulate over time. They forget to unsubscribe on long-lived components.
// Order during creation:
// 1. ngOnChanges (if @Input exists)
// 2. ngOnInit
// 3. ngDoCheck
// 4. ngAfterContentInit
// 5. ngAfterContentChecked
// 6. ngAfterViewInit
// 7. ngAfterViewChecked
// On destroy: ngOnDestroy
The init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) run only once. The check hooks run on every change detection cycle. ngOnDestroy is called last.
Why it matters: Tests mental model of Angular's lifecycle and initialization flow. Understanding order prevents bugs from running code at wrong times.
Real applications: Initialization in ngOnInit, DOM access in ngAfterViewInit, repetitive checks in ngAfterViewChecked, cleanup in ngOnDestroy.
Common mistakes: Developers run initialization in wrong hooks. They don't understand init vs check hook differences. They expect one-time hooks that actually repeat.
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class MyComponent {
constructor() {
this.data$.pipe(
takeUntilDestroyed() // auto-unsubscribes on destroy
).subscribe(val => this.value = val);
}
}
takeUntilDestroyed() uses Angular's DestroyRef internally. You can also inject DestroyRef directly for more control over cleanup timing.
Why it matters: Tests knowledge of modern Angular (16+) RxJS integration and simplified cleanup patterns. Shows you know the latest best practices.
Real applications: New Angular applications use takeUntilDestroyed instead of manual ngOnDestroy. This is the recommended standard for all new projects.
Common mistakes: Developers still use the old takeUntil + Subject pattern in new projects. They don't know about takeUntilDestroyed operator. They manually implement ngOnDestroy unnecessarily.
ngAfterViewChecked() {
// Runs after every change detection in this view
const height = this.elementRef.nativeElement.offsetHeight;
console.log('View checked, height:', height);
}
This hook pairs with ngAfterViewInit — the init runs once, checked runs repeatedly. Avoid modifying bound properties here to prevent ExpressionChangedAfterItHasBeenCheckedError.
Why it matters: Tests understanding of repeated lifecycle hooks and performance implications. Shows you can implement efficient post-render logic.
Real applications: Measuring element dimensions after render, adjusting scroll positions, triggering animations after layout complete.
Common mistakes: Developers run expensive operations in ngAfterViewChecked, hurting performance. They change state here causing ExpressionChangedAfterItHasBeenCheckedError. They don't understand "Checked" means it runs repeatedly.
@ContentChild('projectedItem') item!: ElementRef;
ngAfterContentChecked() {
if (this.item) {
console.log('Projected content checked');
}
}
This hook tells you when projected content has been re-checked. It pairs with ngAfterContentInit which runs only once during initialization.
Why it matters: Tests understanding of content projection lifecycle and ng-content patterns. Shows you know how to monitor projected content changes.
Real applications: Wrapper components monitoring projected content, tabs tracking tab content changes, form components watching field updates.
Common mistakes: Developers confuse ngAfterContentChecked with ngAfterContentInit. They run expensive logic here repeatedly hurting performance. They don't use it correctly for content observation.
@Directive({ selector: '[appTracker]' })
export class TrackerDirective implements OnInit, OnDestroy {
ngOnInit() { console.log('Directive initialized'); }
ngOnDestroy() { console.log('Directive destroyed'); }
}
Directives are simpler than components because they have no view. Use ngOnChanges to react to input changes and ngOnDestroy to remove event listeners.
Why it matters: Tests understanding that lifecycle hooks apply beyond components. Shows you can implement reusable directive logic with lifecycle management.
Real applications: Attribute directives using ngOnInit to set up behaviors, ngOnChanges to react to condition updates, ngOnDestroy to clean up listeners.
Common mistakes: Developers think lifecycle hooks only work in components. They forget directives need cleanup. They don't understand directives have simpler lifecycles without view hooks.
@ViewChild('myInput') inputRef!: ElementRef;
ngOnInit() {
// inputRef is NOT available here
this.loadData();
}
ngAfterViewInit() {
// inputRef IS available here
this.inputRef.nativeElement.focus();
}
@ViewChild references are only guaranteed to be available in ngAfterViewInit and later hooks. Accessing them in ngOnInit will result in undefined.
Why it matters: Tests knowledge of template DOM access timing. Shows you understand when ViewChild references become available.
Real applications: Using ngOnInit for data fetching, ngAfterViewInit for DOM manipulation like focus, scrolling, or DOM measurements.
Common mistakes: Developers try to use @ViewChild in ngOnInit, getting undefined. They don't understand template is rendered after initialization. They access it too early causing runtime errors.
// This causes ExpressionChangedAfterItHasBeenCheckedError
ngAfterViewInit() {
this.title = 'Updated'; // ERROR in dev mode
}
// Fix: use setTimeout
ngAfterViewInit() {
setTimeout(() => this.title = 'Updated');
}
The error only appears in development mode as a safety check. In production, Angular skips the second verification pass for performance reasons.
Why it matters: Tests understanding of Angular's development safety checks and debugging ngAfterViewInit errors. Shows you know how to work within Angular's constraints.
Real applications: Updating component properties after view renders needs setTimeout() wrapper. This is common when modifying state based on DOM measurements.
Common mistakes: Developers see ExpressionChangedAfterItHasBeenCheckedError and are confused. They don't wrap in setTimeout, continuing to get errors. They don't understand it's development-only safety checking.