The open blogging platform. Say no to algorithms and paywalls.

Async/Await in Angular ngOnInit using TypeScript Decorator

These TypeScript method decorators make lazy loading easy by making sure that certain async functions are always called before others.

image Lots of times, there is a need to load data using Promises from API before the page loads or Class initialization. To achieve this I see many of my fellow devs use async on ngOnInit so they can await on data fetch API method inside ngOnInit

async ngOnit {
  this.movies = await this.service.getMovies();
}

But if you see carefully there is no one awaiting ngOnInit and you can’t await ngOnInit even if you wanted to. It will run the async function but it WILL NOT await for it to complete, it just allows you to use the await keyword, but it is not a aysnc function, despite having the async keyword. It looks kind of awkward and somewhat hard to comprehend, doesn’t seem conventional. Ideally, the way is to use route resolvers so that data is loaded before the route finishes navigating and I can know the data is available before the view is ever loaded. A lot of Stack Overflow answers point to a more readable way of doing it without adding async keyword to ngOnInit but the solution behaves the same.

ngOnInit() {
  (async () => {
    this.movies = await this.service.getMovies();
  });
}

Also there is a interesting take by

Jonathan Gamble

on Forcing Angular to Wait on Your Async Function a unique way to await components constructor. And this is not just for Angular components. Imagine a simple class that loads some data and also has getters and setters for that data.

class MovieService {
  private movies: Movie[];
  constructor() {
    this.loadMovies();
  }
  getMovieById(id: number) {
    return this.movies.find((movie) => movie.id === id);
  }
  getAllMovies() {
    return this.movies;
  }
  private async loadMovies() {
    this.movies = await MovieStore.getTodos();
  }
}

In above example, the data will start loading immediately when the instance of the class is created and the constructor can’t be blocked until the data is fetched completely. So if getters are called while the request is still pending, it might end up in default empty or undefined movies object. So you can only hope that the data will be loaded by the time any getters are called. Turn around for this is to add some lazy loading and make accessors await the load request something like below.

async getMoviesById(id: number){
  if(!this.movies){
    await this.loadMovies();
  }
  return this.movies.find(movie => movie.id === id);
}

Looks good right, but this code now becomes a duplicate code and the load request might be fired more than once only if we could find a way to abstract all this wiring with something else.

TypeScript Decorators to the rescue 🎉

A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Short and crisp, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. A good way to abstract the wiring would be using decorators somewhere we can wait for called all methods which are to be run and completed first, and then run the ones which are dependent on those. Something like below: Here’s how to make these decorators work. You can add each decorator to as many methods as you’d like.

Code explanation for those who are new to Decorators

To understand the init/waitForInit decorator code we first must understand how the decorators are evaluated. TypeScript documentation on Decorators here does a wonderfull job of explaing how decorators are evaluated, I will recommend checking it. Let’s take the below example to understand the evaluation: Same as above, all our init decorated methods would be evaluated first, where in the movies service case, loadMovies would be registered as one of the INIT_METHODS and then when any one of the methods decorated with waitForInit like getAllMovies would be called then it would first run the loadMovies Promise first and then run the corresponding calling function. Wait 🤔 We were talking about 'async ngOnInit' where is the code? 👊 Don’t worry, we got you covered. Here is how you can use the same init/waitForInit decorator on ngOnInit . Here is the Stackblitz example demonstrating the said decorators on ngOnInit. https://angular-ivy-kq23e9.stackblitz.io

Concluding, we briefly discussed the implementation on pre-loading the data in any class or before template initialization in ngOnInit in Angular using the TypeScript decorator.

References:




Continue Learning