Async calls in app.component.ts issue

Hello,

I’ve been having issues when starting up my app due do async methods being called inside initializeApp(). Maybe I just don’t know the correct place to to the required startup routines as I didn’t see any other topic with this issue.

When starting up the app I need to hit localstorage to see if there’s already some authentication info saved there, as well as other user configuration options. This is my code, stripped of some out of the scope, synchronous stuff.

constructor(private platform: Platform, private configService: ConfigService, private authService: AuthService, private pushService: NotificationService, private locationService: LocationService, private eventService: Events, private screenOrientation: ScreenOrientation) {
    this.initializeApp();
  }

  initializeApp() {
    this.platform.ready().then(async () => {

      await this.authService.fillAuthData();

      if (this.authService.IsAuthenticated) { //promise that reads and writes using @ionic/storage 
        await this.configService.getUserConfig(); //promise that reads and writes using @ionic/storage 
        await this.pushService.getNotificationSettingsFromStorage(); //promise that reads and writes using @ionic/storage
        await this.locationService.getLocation(); //promise that makes an http get call for state/city names
        this.authService.redirectOnLogin();
      }
      else if (await this.authService.increaseAppRunCount() == 0) {  //promise that reads and writes using @ionic/storage
        this.authService.redirectToWelcomePage();
      }
      if (Capacitor.isPluginAvailable("SplashScreen")) {
        SplashScreen.hide();
      }

    });
  }

So, as soon as a Promise returning function is called using await, like authService.fillAuthData(), the code execution goes back to whoever called the app.component.ts constructor. And everything else starts going on, like the default start route getting navigated to as my route guard breakpoints get hit.
The page for the default route expects the location to be ready. Also expects the authentication info to be there, but it isn’t. The route guard routes back to the login page. Well, you get the picture.

By the time SplashScreen.hide(); is called the splash screen is long gone as, apparently, the router.navigateByUrl() calls in the route guard make it go await.

All @ionic/storage methods are async and I cant avoid this behavior. What is the best approach here? Where should I do this startup routines on app start?

This is a bit of an opinion question, and my opinions are frequently not shared by very many other people, so feel free to keep waiting for others to chime in.

My answer is that you should not think of these as “startup routines”, because that framing is squarely stuck in an imperative mindset: “yo computer, I’m in charge here: do A, then B, then C”. Trying to write webapps like that made me bash my head into a wall for about 6 weeks when I was starting out.

Service providers are just that: they provide services. Which means that the only time you should be calling their methods is right when they are needed. I don’t know if all of those Promises you’re getting and then not doing anything with are interconnected. Maybe you don’t either, and maybe that’ll change later. Trying to jam all that logic into a single place doesn’t buy you anything useful: all it does is make the code harder to read because stuff over there is dependent on what went down way over here in initializeApp.

The one thing you probably need to do “right now” at app startup is “figure out what landing page to send the user to”, and you can use route guards to do that in a more scalable fashion.

I’m especially concerned about this line:

if (this.authService.IsAuthenticated) { //promise that reads and writes using @ionic/storage

If that comment is accurate, then that branch will always be taken. You’re testing whether the Promise exists, not what it resolved to (or even if it has yet). Again, I would not have any of this logic in the app component, but wherever it does end up needs to look like:

this.authService.isAuthenticated().then(authed => {
  if (authed) {
    // woo hoo
  } else {
    this.router.navigate(["/login"]);
  }
})

So, in summary, I don’t think the right question is “how do I set up the whole house of cards at the beginning”, because that’s fragile and unidiomatic. Every part of your app needs to be responsible for ensuring that it has the necessary information to do its job, and you need to let go of trying to micromanage what executes when.

2 Likes

You know what? You’re absolutely right.
I’m going to take services calls out of initializeApp.

if (this.authService.IsAuthenticated) { //promise that reads and writes using @ionic/storage

I added that commeent there by mistake. It’s actually for the line above: await this.authService.fillAuthData();

Thanks for your time looking at my issue and the effort to point me to a direction, explaining the why’s.