LiveReload with lazy loading resetting Providers but stays on the same page


#1

In short, the problem that I’m having is that, when I use “ionic serve -c -l” or “ionic cordova run android -c -l”, it runs fine the first time but I run into problems when ever I make changes to the code and it live-reloads.

The problem is that my “app.component” need to do a few async operations before displaying my first page (like figuring out which data server url to use for transactions (dev or prod)). After completing, it displays the first page where the ionViewDidLoad sends an http request to the data server url and then I display my data. I’m now working on implementing Lazy Loading and the problem I’m having is that now when ever I modify some code and it rebuilds/reloads, it stays on the page where it was but seems to reset everything else (including my dataProvider and the data server url). This has the effect that my since it restarts the app from the last page I was on, my ionViewDidLoad tries to fire the transaction again but the root code hasn’t had time to get my data server url (so I get an error!). A few ms later, I see the root code finish running and displaying the first page again (which now runs fine since my data server url is now set).

The annoying result is that now when ever I modify code, it rebuilds/reloads and I get an error that pops up on screen. I have noticed (with “serve”) that the url in the address bar follows the page you are currently viewing such as http://localhost:8100/#/nav/n4/home

When I modify code and it rebuilds/reloads, it stays at this address while the app.component code runs. Is this how things work now? Or am I doing something wrong? I know this won’t happen when the app runs in production without live-reload but during development, this is annoying as hell! Is there a setting I can change to tell Ionic to go back to the root page when ever I rebuild/reload? Just like it used to do with Ionic 2?


#2

My Ionic Info:
global packages:

@ionic/cli-utils : 1.5.0
Cordova CLI      : 7.0.1
Ionic CLI        : 3.5.0

local packages:

@ionic/app-scripts              : 2.0.0
@ionic/cli-plugin-cordova       : 1.4.1
@ionic/cli-plugin-ionic-angular : 1.3.2
Cordova Platforms               : android 6.2.3
Ionic Framework                 : ionic-angular 3.5.0

System:

Node       : v6.10.3
OS         : Windows 10
Xcode      : not installed
ios-deploy : not installed
ios-sim    : not installed
npm        : 3.10.8

#3

I’m facing the exact same issues (using Firebase as Backend). It didn’t happen before upgrading to the latest version. Did you solve your problem ?


#4

If you’re using lazy loading then this is how it works. When using @IonicPage it will allow you to go directly to a specific part of an application by going to a specific URL, i.e:

localhost:8100/#/news

Would set the news page as the root page. If that doesn’t make sense for the structure of your application (i.e. it shouldn’t ever be the root page) then you can use defaultHistory to set up the appropriate navigation stack in the case that someone goes to that URL (which could allow a user to load that page directly, and then still go back to a home page).

The other side effect of this is the issue you are running into - loading a sub page directly causes problems with the structure of your application. Live reload will just refresh whatever the page is currently on. It may be possible to get live reload to instead always load the root URL I’m not sure, but the better solution would be to modify your application to support loading in from any page (even if that means just checking in the ionViewDidLoad and kicking the user back to your normal root page if the conditions aren’t correct.)


#5

Hi Josh, thanks for you answer (love your tutorials by the way!). The second part of your answer is the direction I’m taking. In summary, when running an app from the start, it runs the app.component.ts code before selecting a page to display which means you can establish a few things before getting started (connect to local dev server or production? Go to login or user’s home page? etc…) but when we go to a page, modify some code and it recompiles/live-reloads, it will keep that page as the root page but reset everything else (including the data you’ve accumulated in your DataProvider). The app.component.ts restarts and takes a moment to resolve the data again (server address, logged in?..) but the page which remains as the root for a moment runs as if all of this was already resolved.

Solution 1: In your DataProvider, add a variable called “ready” and in the OnInit of your pages, only launch transactions if DataProvider.ready is true. Otherwise, do nothing. This prevents doing transactions from a DP(data provider) which is not ready. The app.component will run, get the DP ready and then select a first page.

Solution 2: Turn your DP’s “ready” into a promise. When your dataprovider is finished resolving data and becomes ready to do transactions, resolve the promise. This means that any page where you launch DP transactions should wait on this promise before making calls to the DP. ex:

this.dataProvider.isReady().then(()=>{ dataProvider.startLoadingMyPageData(); })

The problem with the second solution is that since your app.component starts running again in the background after a live-reload, a new page will get selected eventually which means your page’s OnInit will get run twice so you might see the transactions go through twice.


#6

A quick update for those who intend to compile their app as a mobile app as well as a web page (when using ionic serve, you’re basicaly running it as a web page). We now have to consider that a user could start the app by accessing any subpage directly. ie.: www.mydomain.com/#/schedule

Originally, I could trust that my app would always start from the root of the application (since it was compiled exclisively as a mobile app), have time to load async data (JWT token from LocalStorage for example) and then select which page to display first (Login or Schedule). Now, we have to assume that a user could access any subpage directly. The problem was that a page like “MySchedule” assumes that we’re already logged in and may attempt to load data from a rest service in the ionDidLoad function before the root code has time to load your user’s stored token (or even check if he has one). So the trick to avoid having your pages launch transactions before your root code has finished loading async data is to use guards.

In a page’s lifecycle, there is a function called ionViewCanEnter(). You can use this function to return true/false or even a Promise which will in turn return true/false. If this function returns false, the page does not show up. The trick is to get a promise which only gets resolved once all your root async data finishes loading.

ionViewCanEnter(){
    return this.dataProvider.getReadyPromise()
  }

You can use the same method to also checks if the user has a token in case a user needs to be logged in before accessing this page:

ionViewCanEnter(){
    return this.dataProvider.getReadyPromise()
        .then(()=>{
             return (this.dataProvider.isLoggedIn())
         })
  }

The last part to this problem is to have your app.component.ts decide if it should navigate to a page after loading data from localStorage. If a user is accessing your page without specifying any subpage (ie: www.mydomain.com), your app.component.ts should make the decision to navigate to Login or MySchedule depending on if the user has a token or not.

In the case where your app is running as a web page, you could use the following simple logic by checking for a route after the ‘#’ in your url:


let routeParts = document.URL.split('/#/');
   if (routeParts.length <= 1){
       console.log("Short route.  Selecting a first page.");
       this.selectFirstPage();
   }
   else {
      console.log("Long route: NOT selecting a first page since one was already selected.");
   }

I’m sure there must be a cleaner way of doing this but it works. (I have yet to check how this will behave when running as an app).


#7

Hello Loki, i’m facing the same problem as you. I’m using firebase database, and have two pages: LoginPage and MenuPage, if a user is loged in, the app will send to MenuPage (this Menu is a side menu and has a FeedPage as rootPage, and others pages). Then, when running the app first time, that runs ok but, when I rebuild or reload the page (in url: “/#/menu/feed”), first looks like that i don’t have connection with the database because it’s first loaded the menupage.ts, in sequence app.components.ts and after turn back to menupage and it’s loaded ok.
Did you have solved your problem?


#8

Only when I use this code, the app runs two time:

const authObserver = afAuth.authState.subscribe(
      users => {
        if (users) {
          this.rootPage = 'MenuPage';
          authObserver.unsubscribe();
        } else {
          this.rootPage = 'LoginPage';
          authObserver.unsubscribe();
        }
      }
    );

When I use this, it runs one time, but starts from the page in use (for exemplo, url: “/#/menu/feed”):

afAuth.auth.onAuthStateChanged(function (user) {
      if (user) {
        this.rootPage = 'MenuPage';
        console.log('MENU')
      } else {
        this.rootPage = 'LoginPage';
        console.log('LOGIN')
      }
    });