🐶
Angular

Angular Constructor vs. ngOnInit: Key Differences Explained

By Filip on 10/05/2024

This article explores the distinct roles and optimal use cases of constructors and ngOnInit in Angular component initialization.

Angular Constructor vs. ngOnInit: Key Differences Explained

Table of Contents

Introduction

This article explains the differences between the constructor and ngOnInit in Angular, both of which are essential for component initialization but serve distinct purposes. It will first discuss the constructor, a fundamental JavaScript concept for creating class instances and handling dependency injection. Then, it will delve into ngOnInit, an Angular lifecycle hook that allows for initialization tasks after the component's input properties are bound. The article will provide examples for both, highlighting their respective use cases and best practices. Finally, it will summarize the key takeaways and emphasize the importance of choosing the appropriate method for different initialization scenarios in Angular development.

Step-by-Step Guide

In Angular, both the constructor and ngOnInit are crucial for component initialization, but they serve different purposes and have distinct execution timings. Let's break down their differences and when to use each:

1. Constructor: The JavaScript Foundation

  • Purpose: The constructor is a fundamental concept in JavaScript classes, not specific to Angular. It's called when a new instance of a class is created.
  • Primary Role:
    • Initializing class members: Use it to set up default values for your component's properties.
    • Dependency Injection: Inject services that your component relies on. These services are provided through Angular's dependency injection mechanism.

Example (JavaScript):

class MyComponent {
  constructor(private myService: MyService) {
    // Initialize a property
    this.name = 'Default Name';

    // Use the injected service
    this.myService.fetchData(); 
  }
}

2. ngOnInit: The Angular Lifecycle Hook

  • Purpose: ngOnInit is a lifecycle hook provided by Angular. Lifecycle hooks allow you to tap into specific moments of a component's life, from creation to destruction.
  • Execution: Angular calls ngOnInit after the constructor and after the component's input properties have been bound.
  • Primary Role:
    • Initialization logic dependent on input properties: Perform actions that require the component's input values to be available.
    • Complex initialization: Execute more involved setup logic that might involve data fetching or DOM manipulation (ideally after Angular has rendered the initial view).

Example (JavaScript):

class MyComponent {
  @Input() userId: string;

  ngOnInit() {
    // Fetch data based on the input property
    this.userService.getUser(this.userId)
      .subscribe(user => this.user = user);
  }
}

In Summary:

  • Constructor: Use for basic initialization, dependency injection, and tasks that don't depend on the component's view or input properties.
  • ngOnInit: Use for initialization tasks that require the component's view to be initialized or depend on input properties being available.

Key Points:

  • Avoid heavy computations or side effects within the constructor, as it might impact application performance.
  • ngOnInit provides a cleaner separation of concerns, ensuring that initialization logic dependent on the component's state is executed at the appropriate time.

Code Example

This TypeScript code defines an Angular component named MyComponent. It demonstrates constructor and ngOnInit lifecycle hooks, dependency injection, and asynchronous data fetching. The component fetches data from a simulated service and displays it. It also receives a userId as input and fetches user data using another service. The code showcases how to use injected services, initialize properties, and handle asynchronous operations within the component's lifecycle.

import { Component, OnInit, Input } from '@angular/core';

// Simulating a service for demonstration
class MyService {
  fetchData() {
    console.log('Data fetched from MyService!');
  }
}

// Simulating a user service
class UserService {
  getUser(id: string) {
    // Simulating an asynchronous data fetch
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ id, name: `User ${id}` });
      }, 1000);
    });
  }
}

@Component({
  selector: 'app-my-component',
  template: `
    <p>Name: {{ name }}</p>
    <p *ngIf="user">User: {{ user.name }}</p>
  `,
})
export class MyComponent implements OnInit {
  name: string;
  @Input() userId: string;
  user: any;

  // Injecting services through the constructor
  constructor(private myService: MyService, private userService: UserService) {
    // Constructor: Called first when the component is created
    console.log('Constructor called!');

    // Initializing a property
    this.name = 'Default Name';

    // Using the injected service
    this.myService.fetchData();
  }

  // ngOnInit: Called after the constructor and after the first input property binding
  ngOnInit() {
    console.log('ngOnInit called!');

    // Fetching data based on the input property
    if (this.userId) {
      this.userService.getUser(this.userId).then(user => {
        this.user = user;
      });
    }
  }
}

Explanation:

  1. Constructor:

    • We inject MyService and UserService using Angular's dependency injection.
    • We initialize the name property to 'Default Name'.
    • We call this.myService.fetchData() to demonstrate using an injected service.
  2. ngOnInit:

    • We check if userId is available (passed as an input).
    • If available, we use this.userService.getUser() to fetch user data based on the userId.
    • We update the user property with the fetched data.

Key Points:

  • The constructor is called first, followed by ngOnInit.
  • Dependency injection happens in the constructor.
  • ngOnInit is the appropriate place to perform initialization that depends on input properties or the component's view being ready.

This example demonstrates the distinct roles of the constructor and ngOnInit in an Angular component's lifecycle.

Additional Notes

  • Constructor Overloading: While you can technically have multiple constructors in TypeScript (using method overloading), Angular's dependency injection only works with a single constructor.
  • Component Lifecycle: Understanding the full component lifecycle (including ngOnChanges, ngDoCheck, etc.) is essential for writing robust Angular applications. ngOnInit is just one piece of the puzzle.
  • Testing: When unit testing components, you'll often interact with both the constructor (for mocking dependencies) and ngOnInit (for testing initialization logic).
  • Performance Considerations: While rare, extremely complex initialization logic in ngOnInit could potentially impact performance. In such cases, consider optimizing or breaking down the logic.
  • Angular Universal (Server-Side Rendering): Be mindful of using DOM manipulation within ngOnInit, as it might not behave as expected in server-side rendering scenarios.
  • Alternative Initialization: For simple property assignments based on inputs, consider using property binding directly in your template instead of ngOnInit. For example: <p>User ID: {{ userId }}</p>.
  • Angular Change Detection: Changes made within the constructor are not automatically reflected in the view. Angular's change detection cycle kicks in after the constructor, so changes made in ngOnInit and subsequent lifecycle hooks will be detected and reflected.
  • Best Practices:
    • Keep the constructor lean and focused on dependency injection and simple initializations.
    • Use ngOnInit for more complex initialization tasks that depend on the component's view or input properties.
    • Follow a consistent coding style and clearly document your initialization logic for better maintainability.

Summary

Feature Constructor ngOnInit
Type JavaScript class method Angular lifecycle hook
Timing Called before ngOnInit, when a component instance is created Called after the constructor and after input properties are bound
Purpose - Basic initialization of class members
- Dependency injection
- Initialization logic that depends on input properties
- Complex initialization tasks (e.g., data fetching, DOM manipulation)
Best Practices - Keep it lightweight; avoid heavy computations or side effects - Use for tasks that require the component's view or input properties

In essence:

  • Constructor: Sets the stage for your component.
  • ngOnInit: Performs actions once the stage is set and the curtain is about to rise.

Conclusion

In conclusion, while both the constructor and ngOnInit play vital roles in an Angular component's lifecycle, they address different needs. The constructor, a core JavaScript concept, initializes class members and handles dependency injection. It's called first when the component is created. On the other hand, ngOnInit, an Angular lifecycle hook, executes after the constructor and after the first round of input property binding. This makes it ideal for initialization tasks that depend on the component's view being ready or require access to input properties. Understanding the distinct roles and execution timings of these two methods is crucial for writing well-structured, efficient, and maintainable Angular applications. By adhering to best practices and leveraging each method appropriately, developers can ensure their components are initialized correctly and function as intended within the Angular framework.

References

Were You Able to Follow the Instructions?

😍Love it!
😊Yes
😐Meh-gical
😞No
🤮Clickbait