Constructor vs ngOnInit in Angular
Question 1: What are the key differences between Constructor and ngOnInit?
Answer: Key differences include:
Timing:
- Constructor: Called when class is instantiated
- ngOnInit: Called after all properties are initialized
Purpose:
- Constructor: Basic initialization, DI setup
- ngOnInit: Complex initialization, data fetching
Access to Properties:
- Constructor: Limited access to properties
- ngOnInit: Full access to all properties
Question 2: How do you use Constructor and ngOnInit in modern Angular?
Answer: Here are modern implementation patterns:
// Modern Dependency Injection
@Component({
selector: 'app-user',
template: `
@if (user(); as userData) {
<div>{{ userData.name }}</div>
}
`
})
export class UserComponent implements OnInit {
// Modern DI using inject()
private userService = inject(UserService);
private router = inject(Router);
private destroy$ = inject(DestroyRef);
// Signals for state management
user = signal<User | null>(null);
loading = signal(false);
// Computed values
userStatus = computed(() =>
this.user()?.status || 'inactive'
);
constructor() {
// Only basic initialization
console.log('Component instantiated');
}
ngOnInit() {
// Complex initialization
this.loading.set(true);
this.userService.getUser().pipe(
takeUntilDestroyed(this.destroy$)
).subscribe({
next: (user) => {
this.user.set(user);
this.loading.set(false);
},
error: (error) => {
this.handleError(error);
this.loading.set(false);
}
});
}
}
// Service Pattern
@Injectable({ providedIn: 'root' })
export class DataService {
private http = inject(HttpClient);
private errorHandler = inject(ErrorHandler);
// State management
private dataSubject = signal<Data[]>([]);
readonly data = this.dataSubject.asReadonly();
constructor() {
// Basic service setup
this.initializeService();
}
private initializeService() {
// Service-specific initialization
console.log('Service initialized');
}
loadData() {
return this.http.get<Data[]>('/api/data').pipe(
tap(data => this.dataSubject.set(data)),
catchError(error => {
this.errorHandler.handleError(error);
return EMPTY;
})
);
}
}
Question 3: What are the best practices for using Constructor and ngOnInit?
Answer: Here are best practices with examples:
// 1. Constructor Best Practices
@Component({
selector: 'app-best-practices',
template: `
@if (isLoading()) {
<loading-spinner />
} @else {
<data-display [data]="data()" />
}
`
})
export class BestPracticesComponent implements OnInit {
// 1. Use inject() for DI
private service = inject(DataService);
private destroy$ = inject(DestroyRef);
// 2. Initialize signals and subjects
data = signal<Data | null>(null);
isLoading = signal(false);
private refresh$ = new Subject<void>();
constructor() {
// 3. Only basic initialization
this.setupRefreshLogic();
}
private setupRefreshLogic() {
// 4. Setup streams in constructor
this.refresh$.pipe(
takeUntilDestroyed(this.destroy$),
switchMap(() => this.loadData())
).subscribe();
}
ngOnInit() {
// 5. Complex initialization in ngOnInit
this.loadData();
}
private loadData() {
this.isLoading.set(true);
return this.service.getData().pipe(
tap({
next: data => {
this.data.set(data);
this.isLoading.set(false);
},
error: error => {
this.handleError(error);
this.isLoading.set(false);
}
})
);
}
}
// 2. Form Component Pattern
@Component({
selector: 'app-user-form',
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="name" />
<input formControlName="email" />
<button type="submit">Submit</button>
</form>
`
})
export class UserFormComponent implements OnInit {
private fb = inject(FormBuilder);
private userService = inject(UserService);
// Initialize form in constructor
form = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
ngOnInit() {
// Load data and patch form
this.userService.getCurrentUser().pipe(
take(1)
).subscribe(user => {
this.form.patchValue(user);
});
}
}
Interview Tips 💡
Initialization Patterns
@Component({...}) export class PatternComponent implements OnInit { // 1. Dependencies via inject() private service = inject(Service); // 2. Signals and state data = signal<Data | null>(null); constructor() { // 3. Basic setup only this.setupStreams(); } ngOnInit() { // 4. Complex initialization this.loadData(); } }
Error Handling
@Injectable({ providedIn: 'root' }) export class ErrorService { private errorSubject = new Subject<Error>(); constructor() { // Setup global error handling this.setupGlobalHandling(); } ngOnInit() { // Initialize error listeners this.initializeListeners(); } }
Testing
describe('Component', () => { it('should initialize properly', () => { const component = new Component(); // Constructor assertions expect(component).toBeTruthy(); // ngOnInit assertions component.ngOnInit(); expect(component.data()).toBeDefined(); }); });
Performance
@Component({ changeDetection: ChangeDetectionStrategy.OnPush }) export class OptimizedComponent { constructor() { // Lightweight constructor } ngOnInit() { // Defer heavy operations setTimeout(() => { this.heavyOperation(); }); } }
Common Patterns
@Component({...}) export class CommonComponent implements OnInit { constructor() { // DO: Basic initialization this.setupConfig(); } ngOnInit() { // DO: Complex operations this.loadData(); this.setupSubscriptions(); } }
Best Practices
@Component({...}) export class BestPracticeComponent implements OnInit { // DO: Use inject() private service = inject(Service); // DO: Initialize simple properties data = signal<Data[]>([]); // DON'T: Heavy operations in constructor constructor() { console.log('Simple initialization'); } // DO: Complex operations in ngOnInit ngOnInit() { this.loadData(); } }
Remember: In interviews, focus on:
- Timing differences
- Proper usage
- Best practices
- Error handling
- Testing approaches
- Performance considerations
- Common pitfalls
Key points to emphasize:
- Constructor for DI and basic setup
- ngOnInit for complex initialization
- Error handling patterns
- Testing strategies
- Performance optimization
- Best practices
- Common use cases