In Angular, components are the foundation of any application. Each component has a lifecycle, which defines key moments in its existence—from creation to destruction. One of the most important lifecycle hooks is ngOnInit, which is called after Angular initializes a component and its input properties. Understanding ngOnInit is crucial for performing initialization logic, such as fetching data, setting default values, or configuring component state before rendering.
In this comprehensive guide, we will explore:
- The role of ngOnInit in Angular components
- How to perform data fetching and initialization logic
- Differences between the constructor and ngOnInit
- Practical examples with API calls and default value initialization
- Best practices for using ngOnInit effectively
Understanding ngOnInit
ngOnInit
is a lifecycle hook provided by Angular. It is part of the OnInit interface and is called once after Angular has initialized the component and set its input properties.
Key Characteristics of ngOnInit
- Initialization After Inputs Are Set: ngOnInit runs after Angular sets the component’s bound input properties.
- Runs Once Per Component Instance: Unlike ngOnChanges, ngOnInit is called only once.
- Ideal for Initialization Logic: Fetching data, configuring variables, or initializing services.
- Supports Dependency Injection: You can safely use injected services for initialization inside ngOnInit.
Syntax
To use ngOnInit, implement the OnInit
interface:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-profile',
template: <h1>{{ name }}</h1>
})
export class ProfileComponent implements OnInit {
name: string = '';
ngOnInit() {
this.name = 'John Doe';
console.log('ProfileComponent initialized with name:', this.name);
}
}
- The
implements OnInit
is optional but recommended. - ngOnInit is automatically called by Angular after component initialization.
How to Perform Data Fetching or Initialization Logic
1. Fetching Data from Services
Most Angular applications retrieve data from REST APIs or services. ngOnInit is the ideal place to call these services, ensuring that data is fetched after component creation but before rendering.
Example: Fetching User Data
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `
<h2>Users</h2>
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: any[] = [];
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getUsers().subscribe((data) => {
this.users = data;
console.log('User data loaded:', this.users);
});
}
}
userService.getUsers()
returns an observable of user data.- ngOnInit ensures that the data is fetched after the component has been created.
- Using the constructor for API calls is not recommended (explained below).
2. Setting Default Values
ngOnInit can also be used to initialize component properties with default values:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-dashboard',
template: `
<h1>Welcome, {{ username }}</h1>
<p>Status: {{ status }}</p>
`
})
export class DashboardComponent implements OnInit {
username: string = '';
status: string = '';
ngOnInit() {
this.username = 'Alice';
this.status = 'Active';
console.log('Dashboard initialized with default values');
}
}
- Properties can be initialized based on logic or configuration.
- Ensures a consistent initial state before rendering.
3. Combining Initialization Logic
You can combine multiple initialization tasks in ngOnInit:
ngOnInit() {
// Set default values
this.username = 'Alice';
this.status = 'Active';
// Fetch data from API
this.userService.getUsers().subscribe((data) => {
this.users = data;
});
// Initialize timers or intervals
this.refreshInterval = setInterval(() => {
console.log('Refreshing data...');
}, 5000);
}
- ngOnInit can handle multiple tasks efficiently.
- Avoid heavy computation; delegate intensive tasks to services.
Difference Between Constructor and ngOnInit
It is common for new Angular developers to confuse the constructor with ngOnInit. While both can execute code, they serve different purposes:
Feature | Constructor | ngOnInit |
---|---|---|
When executed | Immediately when the component class is instantiated | After Angular sets component inputs |
Ideal use | Dependency injection, lightweight initialization | Data fetching, property initialization, calling services |
Lifecycle awareness | Not aware of inputs yet | Inputs are initialized |
Multiple calls | Only once | Only once, after inputs are set |
Example: Why Not Use Constructor for Initialization
constructor(private userService: UserService) {
this.userService.getUsers().subscribe((data) => {
this.users = data; // Works, but inputs may not be set yet
});
}
- The constructor runs before Angular sets input properties.
- This can cause errors if the initialization logic depends on inputs.
- ngOnInit is the safe and recommended place for such logic.
Example with API Calls
Let’s create a component that fetches posts from a REST API:
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-posts',
template: `
<h1>Posts</h1>
<ul>
<li *ngFor="let post of posts">{{ post.title }}</li>
</ul>
`
})
export class PostsComponent implements OnInit {
posts: any[] = [];
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get('https://jsonplaceholder.typicode.com/posts')
.subscribe((data: any) => {
this.posts = data;
console.log('Posts loaded:', this.posts.length);
});
}
}
- ngOnInit ensures that HTTP requests are made after component initialization.
- Template displays posts using
*ngFor
directive. - Component is reusable and modular.
Best Practices for Using ngOnInit
- Use ngOnInit for All Initialization
- Avoid performing initialization in the constructor.
- Delegate Heavy Logic to Services
- Keep components lightweight; move API calls, computation, or business logic to services.
- Avoid Side Effects in Templates
- Do not perform complex logic inside the template; initialize it in ngOnInit.
- Combine ngOnInit with Observables
- Use
async
pipe or subscribe in ngOnInit to fetch asynchronous data.
- Use
- Clean Up Resources in ngOnDestroy
- If ngOnInit starts subscriptions, timers, or intervals, clean them up in
ngOnDestroy
.
- If ngOnInit starts subscriptions, timers, or intervals, clean them up in
Advanced Example: Initialization with Dependency Injection
import { Component, OnInit } from '@angular/core';
import { ConfigService } from './config.service';
import { UserService } from './user.service';
@Component({
selector: 'app-dashboard',
template: `
<h1>{{ config.appTitle }}</h1>
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class DashboardComponent implements OnInit {
users: any[] = [];
config: any;
constructor(private configService: ConfigService, private userService: UserService) {}
ngOnInit() {
this.config = this.configService.getConfig();
this.userService.getUsers().subscribe(data => {
this.users = data;
});
}
}
- Injected services can be safely used in ngOnInit.
- Combines default configuration with dynamic API data fetching.
Common Mistakes
- Fetching data in the constructor – Can lead to undefined input errors.
- Using ngOnInit for extremely heavy synchronous tasks – May block the UI.
- Not unsubscribing from observables started in ngOnInit – Can cause memory leaks.
- Ignoring input changes – Use ngOnChanges if initialization depends on input properties.
Leave a Reply