Skip to main content

When to Use Async in Angular

There are different contexts for "async" in Angular. Let me explain both async/await and the async pipe:

1. Async/Await (JavaScript Feature)

When to Use Async/Await:

Good Use Cases:

Sequential Operations:

async ngOnInit() {
try {
const user = await this.userService.getUser().toPromise();
const profile = await this.profileService.getProfile(user.id).toPromise();
const settings = await this.settingsService.getSettings(profile.id).toPromise();

this.userData = { user, profile, settings };
} catch (error) {
console.error('Error loading data:', error);
}
}

Simple HTTP Calls with Error Handling:

async loadData() {
this.loading = true;
try {
const data = await this.dataService.getData().toPromise();
this.processData(data);
} catch (error) {
this.handleError(error);
} finally {
this.loading = false;
}
}

Form Submission:

async onSubmit() {
if (this.form.valid) {
try {
this.submitting = true;
const result = await this.apiService.submitForm(this.form.value).toPromise();
this.router.navigate(['/success']);
} catch (error) {
this.showError(error.message);
} finally {
this.submitting = false;
}
}
}

Avoid Async/Await When:

Real-time Data Streams:

// DON'T do this
async ngOnInit() {
while(true) {
const data = await this.realtimeService.getData().toPromise();
// This breaks the stream nature of observables
}
}

// DO this instead
ngOnInit() {
this.realtimeService.getData().subscribe(data => {
this.handleRealtimeData(data);
});
}

Multiple Parallel Operations:

// DON'T do this (sequential)
async loadAllData() {
const users = await this.userService.getUsers().toPromise();
const products = await this.productService.getProducts().toPromise();
const orders = await this.orderService.getOrders().toPromise();
}

// DO this instead (parallel)
ngOnInit() {
forkJoin({
users: this.userService.getUsers(),
products: this.productService.getProducts(),
orders: this.orderService.getOrders()
}).subscribe(({ users, products, orders }) => {
// All requests completed in parallel
});
}

2. Async Pipe (Angular Feature)

When to Use Async Pipe:

Always Prefer Async Pipe When:

Displaying Observable Data:

@Component({
template: `
<div *ngIf="user$ | async as user">
<h1>{{user.name}}</h1>
<p>{{user.email}}</p>
</div>

<ul>
<li *ngFor="let item of items$ | async">{{item.name}}</li>
</ul>
`
})
export class UserComponent {
user$ = this.userService.getCurrentUser();
items$ = this.itemService.getItems();

constructor(
private userService: UserService,
private itemService: ItemService
) {}
}

Loading States:

@Component({
template: `
<div *ngIf="loading$ | async">Loading...</div>
<div *ngIf="error$ | async as error" class="error">{{error}}</div>
<div *ngFor="let item of data$ | async">{{item.name}}</div>
`
})
export class DataComponent {
data$ = this.dataService.getData();
loading$ = this.dataService.loading$;
error$ = this.dataService.error$;
}

Benefits of Async Pipe:

  • Automatic subscription/unsubscription
  • No memory leaks
  • Cleaner code
  • Automatic change detection

Don't Use Async Pipe When:

You Need to Transform Data in Component:

// Instead of multiple async pipes
@Component({
template: `
<div>Total: {{(data$ | async)?.length}}</div>
<div>Active: {{(data$ | async)?.filter(item => item.active).length}}</div>
`
})

// Do this
@Component({
template: `
<div *ngIf="processedData$ | async as data">
<div>Total: {{data.total}}</div>
<div>Active: {{data.active}}</div>
</div>
`
})
export class DataComponent {
processedData$ = this.dataService.getData().pipe(
map(data => ({
total: data.length,
active: data.filter(item => item.active).length,
items: data
}))
);
}

Decision Matrix

ScenarioUse Async/AwaitUse Async PipeUse Subscribe
Display data in template
Sequential API calls
Form submission
Real-time data streams
Complex data transformation✅ (with operators)
One-time operations
Component lifecycle dependent✅ (with unsubscribe)

Best Practices Summary

  1. Use async pipe for templates - automatic memory management
  2. Use async/await for imperative operations - form submissions, sequential calls
  3. Use observables with operators for complex data transformations
  4. Avoid mixing patterns - stick to one approach per use case
  5. Always handle errors regardless of the pattern chosen

The key is choosing the right tool for the specific use case!