Ionic app loading sequence


#1

Where can I find information on what all happens internally fron native and/or non-native perspective when an Ionic App is launched or loaded?

For instance, when are the services loaded or what all components and/or services are available in app.components.ts when it is getting loaded or executed, etc.

Thanks.


#2

Generally speaking, I would consider this something that you actually don’t want to know about, because it would encourage you to code in ways that assume specifics that aren’t necessarily consistent or reliable.

You should always wait for Platform.ready() to fire before any interaction with native plugins. Other services that have specific requirements tend also to have their own ready() method (such as ionic-storage). Anything injected by the Angular DI system in a constructor is ready in that constructor. If you get an error when trying to inject something somewhere (such as NavController in the app component), then that tells you you can’t do that.

I would consider anything else about object lifecycle management to be undefined and therefore not to be relied upon.


#3

Thanks for the response @rapropos!

That question really came to my mind because I have a Provider ‘AuthService’ that, as the name suggest, is used for user authentication. I am trying to inject this Provider in my app.component.ts but it’s giving an error as below as the application is launched:

Uncaught Error: Can't resolve all parameters for AuthService: ([object Object], [object Object], [object Object], [object Object], [object Object], ?).
    at syntaxError (main.js:105031)
    at CompileMetadataResolver._getDependenciesMetadata (main.js:118368)
    at CompileMetadataResolver._getTypeMetadata (main.js:118236)
    at CompileMetadataResolver._getInjectableMetadata (main.js:118222)
    at CompileMetadataResolver.getProviderMetadata (main.js:118512)
    at main.js:118441
    at Array.forEach (<anonymous>)
    at CompileMetadataResolver._getProvidersMetadata (main.js:118402)
    at CompileMetadataResolver.getNgModuleMetadata (main.js:118057)
    at JitCompiler._loadModules (main.js:129121)

It all started when I thought of storing and maintaining logged-in user’s information (token) using ionic/storage until the user is not logging off explicitly. To achieve this I came up with the following:

  1. authorized-user.ts - a Provider (AuthorizedUser) that will deal with storing, retrieving and removing of the user info from and into ionic/storage.

  2. Then I inject the above AuthorizedUser class in my AuthService Provider class (auth-service.ts).
    i) In AuthService, upon successful authentication I get a token then I call AuthorizedUser to store this token in ionic/storage which I have instantiated there. I plan to keep all interactions with AuthorizedUser through AuthService.
    ii) I have also added a new method (silentLogin()) in AuthService that basically checks for the stored token (in AuthorizedUser) validity silently.

  3. Like all other Providers I have added AuthorizedUser and AuthService in app.module.ts.

  4. Then, and this is where I am having issues, I am trying to inject AuthService in app.component.ts so that I can call the silent login. If the silent login is successful I set the **rootPage** to my home page otherwise I set the **rootPage** to login page.

I am calling the AuthService’s silent login method from with the this.platform.ready() block so I am assuming everything is setup and all the services are available. But apparently not, I am getting the above said error.

What am I doing wrong? How do you suggest I implement this functionality?

Thank you.


#4

The position of the question mark tells you where the problem is: the last parameter in the constructor of AuthService. Perhaps it is a circular dependency; those can be resolved using forwardRef, but that should be an absolute last resort, as circular dependencies are often indicative of faulty design, because they suggest two classes that are not truly independent of one another.


#5

@rapropos - I really appreciate you taking the time to help here and the community in general! Thanks again for your quick response!

Yeah, that question mark in AuthService constructor is where I have AuthorizedUser specified. However, I don’t think there is circular dependency.

I wasn’t aware of forwardRef but you are right, it’s better to avoid circular dependency altogether (even if it requires rehashing of the design) than applying patches of some sort in various places.

In my case though, the reason I am saying I do not have circular dependency is because I am using AuthorizedUser only in one place that is, AuthService. The only other place I have it is in app.module.ts.
So, AuthService has a reference to AuthorizedUser but AuthorizedUser does not have any reference to AuthService. And for this silent login purpose I am referring only AuthService in app.component.ts.

Still not able to figure out what I am doing wrong.


#6

I’m nervous about the name AuthorizedUser, because it sounds like a data container, and in my experience smart data containers in Angular apps tend to be bad ideas.

Is AuthorizedUser decorated with @Injectable()? Does it have a constructor that is DI-compatible (IOW, having either no parameters or only ones that themselves can in turn be automatically constructed)? If not, and you can’t make it be so, then the DI system will not be able to construct it, and you will need to rethink the design.


#7

@rapropos - yeah, that’s how I was going to use AuthorizedUser. The reason was, if I have to add in the code for silent login in AuthorizedUser I will have duplicate code or the logic for logging in the user in two separate places; in AuthService and AuthorizedUser. This I thought is not a good design so was trying to keep login related code in AuthService.

Yes, AuthorizedUser is decorated with @Injectable. And it does have a constructor with one parameter; storage. Here is the AuthorizedUser class as I have it:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Storage } from '@ionic/storage';

@Injectable()
export class AuthorizedUser {
    constructor(private storage: Storage) {
    }

    setUser(token: string): void {
        this.storage.set('user', token);
    }

    getUser(): Observable<any> {
        return Observable.fromPromise( this.storage.get('user') );
    }

    logoutUser(): Observable<any> {
        return Observable.fromPromise( this.storage.remove('user') );
    }
}

Maybe, I should inject AuthService in AuthorizedUser instead of app.component.ts and do the following:

  1. Add silentLogin() method in AuthorizedUser which will in turn call silentLogin() in AuthService
  2. Inject AuthorizedUser in app.component.ts and call its 'silentLogin()`

#8

As I suggested before, I would go the opposite direction and get all attempted intelligence (including any interaction with Storage) out of AuthorizedUser and into AuthService. I would probably go so far as to get completely rid of AuthorizedUser entirely and simply have AuthService store and deal out tokens directly.


#9

Hmmm…that’s how I had it to begin with…kind of.

“kind of” because instead of having the code in AuthService itself I just put it in AuthorizedUser and injected AuthorizedUser in AuthService. And then injected AuthService in app.component.ts.

Let me try by keeping the code in AuthService, as you mentioned.

Will come back with the results.

Thanks again for showing your patience always and your kind help in how to go about it.


#10

A new problem. Every time I think I got this, something comes up and makes me realize I still have a fairly long way to go. :frowning:

I understand you are busy but would highly appreciate some help to move forward. I am also looking into how I can get past this but thought of putting it here in case you have time to help.
On my side, I am looking into async/wait of Promise to resolve the problem I am facing. Not sure if that’s the right option.

So, I moved my AuthorzedUser code into AuthService. For your reference, I added the three files (AuthService, app.component.ts and app.module.ts) on Plunker to keep this post free of big code. Here is the Plunker link.

Now, the problem I am facing.
Before that, please don’t mind me iterating a known fact: Storage in ionic/storage deals with Promises.

What’s happening is the silentLogin() method in my AuthService class calls the getAuthorizedUser() method (to get the stored access_token) but doesn’t wait for it to return because of which it fails and therefore the app shows LoginPage on the screen instead of HomePage as per the login in attemptSilentLogin() method in app.component.ts.

And right after the above sequence of things happen, the getAuthorizedUser()'s condole.log() prints the token.

I am copying the console.log output here to show the sequence of things:

In MyApp->attemptSilentLogin()...

In AuthService->silentLogin()...

In AuthService->getAuthorizedUser()...

In AuthService->getAuthorizedUser(), storedUserToken retrieved storedUserToken is: -undefined-

In AuthService->silentLogin(), NO USER!

In MyApp->attemptSilentLogin(), allowed is: false

In MyApp->attemptSilentLogin(), User not available, setting rootPage as LoginPage!

OPEN database: _ionicstorage                         plugins/cordova-sqlite-storage/www/SQLitePlugin.js:175 

new transaction is waiting for open operation        plugins/cordova-sqlite-storage/www/SQLitePlugin.js:106 

In LoginPage->constructor().

In LoginPage->ionViewDidLoad().

OPEN database: _ionicstorage - OK                    plugins/cordova-sqlite-storage/www/SQLitePlugin.js:179 

DB opened: _ionicstorage                             plugins/cordova-sqlite-storage/www/SQLitePlugin.js:80 

In AuthService->getAuthorizedUser(), token is: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlZXVkljMVdEMVRrc2JiMzAxc2FzTTVrT3E1...

I would highly appreciate your input on resolving the issue. I know I need to wait for getAuthorizedUser() to retrieve (from Storage) and return the token but don’t know how to implement it. :frowning:

My apologies for the long post.


#11

That’s not the way I would think of it. Any time you call a function that returns a future (Promise or Observable), you have three choices:

  • I don’t care when it returns, or with what. Declare your function to return void and accept that nobody has any idea when the future will resolve.

  • I care, but only within the confines of this function. Declare your function to return void, and handle absolutely everything needing the resolved value inside a then or subscribe block.

  • I care, and so does whoever is calling me. In this case, declare your function to also return some sort of future, and make the very first word you type in the body be return. I don’t care if that feels awkward at first, but if you follow this rule, you will save yourself from 98% of future-related bugs and organically learn the proper operators and idioms to transform futures properly.


How to get value from NativeStorage and save or return you value (see image)
Promises in promises
Import of @types/q not working in my code
Asynchronous passing image from camera to next page
Silent login when token expired?
ionViewCanEnter together with Native Storage
Get data and store locally in storage.get
[DEVICE ONLY] - ERROR TypeError: Cannot read property 'null' of null
(Solved) Bad getting data from local storage
While loop and async function
[DEVICE ONLY] - ERROR TypeError: Cannot read property 'null' of null
#12

You, Sir/Madam @rapropos are absolutely awesome!

Your wisdom is exceptional, colossal!!

Your patience is unparalleled!

Thank you very much for your great and timely help, I really appreciate it very highly!

:point_up_2: got me thinking!
Sometimes, a common man like me does need some words of wisdom in simple language! :smiley:

I can’t thank you enough and yet I want to thank you again! :slight_smile:


#13

Could you please give an example for type 3 with observable?