Constructor vs ngOnInit in Angular

Question 1: What are the key differences between Constructor and ngOnInit?

Answer: Key differences include:

  1. Timing:

    • Constructor: Called when class is instantiated
    • ngOnInit: Called after all properties are initialized
  2. Purpose:

    • Constructor: Basic initialization, DI setup
    • ngOnInit: Complex initialization, data fetching
  3. 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 💡

  1. 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();
      }
    }
  2. 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();
      }
    }
  3. Testing

    describe('Component', () => {
      it('should initialize properly', () => {
        const component = new Component();
        
        // Constructor assertions
        expect(component).toBeTruthy();
        
        // ngOnInit assertions
        component.ngOnInit();
        expect(component.data()).toBeDefined();
      });
    });
  4. Performance

    @Component({
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class OptimizedComponent {
      constructor() {
        // Lightweight constructor
      }
      
      ngOnInit() {
        // Defer heavy operations
        setTimeout(() => {
          this.heavyOperation();
        });
      }
    }
  5. Common Patterns

    @Component({...})
    export class CommonComponent implements OnInit {
      constructor() {
        // DO: Basic initialization
        this.setupConfig();
      }
      
      ngOnInit() {
        // DO: Complex operations
        this.loadData();
        this.setupSubscriptions();
      }
    }
  6. 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:

  1. Constructor for DI and basic setup
  2. ngOnInit for complex initialization
  3. Error handling patterns
  4. Testing strategies
  5. Performance optimization
  6. Best practices
  7. Common use cases

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.