NgOnInit called on every route change

Hello everyone,

I have a problem with routing in my app. I tought that in Ionic on every route change, components are not being rerendered and ngOnInit is only called once. I was suprised that my components are initializing every time on route change…

that’s my package.json

{
  "name": "carin",
  "version": "0.0.1",
  "author": "Ionic Framework",
  "homepage": "https://ionicframework.com/",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "~8.2.14",
    "@angular/core": "~8.2.14",
    "@angular/forms": "~8.2.14",
    "@angular/platform-browser": "~8.2.14",
    "@angular/platform-browser-dynamic": "~8.2.14",
    "@angular/router": "~8.2.14",
    "@capacitor/android": "^2.1.0",
    "@capacitor/core": "2.1.0",
    "@ionic-native/app-launcher": "^5.25.0",
    "@ionic-native/core": "^5.0.0",
    "@ionic-native/file": "^5.23.0",
    "@ionic-native/google-maps": "^5.5.0",
    "@ionic-native/in-app-browser": "^5.25.0",
    "@ionic-native/media": "^5.25.0",
    "@ionic-native/splash-screen": "^5.0.0",
    "@ionic-native/status-bar": "^5.0.0",
    "@ionic/angular": "^5.0.0",
    "cordova-android": "^8.1.0",
    "cordova-browser": "6.0.0",
    "cordova-plugin-app-launcher": "^0.4.0",
    "cordova-plugin-applist2": "^1.0.2-dev",
    "cordova-plugin-file": "^6.0.2",
    "cordova-plugin-filepath": "^1.5.8",
    "cordova-plugin-googlemaps": "git+https://github.com/mapsplugin/cordova-plugin-googlemaps.git#multiple_maps",
    "cordova-plugin-inappbrowser": "^3.2.0",
    "cordova-plugin-media": "^5.0.3",
    "core-js": "^2.5.4",
    "howler": "^2.1.3",
    "ng-circle-progress": "^1.5.1",
    "rxjs": "~6.5.1",
    "tslib": "^1.9.0",
    "wavesurfer.js": "^3.3.3",
    "zone.js": "~0.9.1"
  },
  "resolutions": {
    "@babel/preset-env": "^7.8.7"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.803.20",
    "@angular/cli": "~8.3.23",
    "@angular/compiler": "~8.2.14",
    "@angular/compiler-cli": "~8.2.14",
    "@angular/language-service": "~8.2.14",
    "@capacitor/cli": "2.1.0",
    "@ionic/angular-toolkit": "^2.1.1",
    "@ionic/lab": "3.1.2",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "^5.0.0",
    "cordova-plugin-device": "^2.0.2",
    "cordova-plugin-ionic-keyboard": "^2.2.0",
    "cordova-plugin-ionic-webview": "^4.1.3",
    "cordova-plugin-splashscreen": "^5.0.2",
    "cordova-plugin-statusbar": "^2.4.2",
    "cordova-plugin-whitelist": "^1.3.3",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.0",
    "npm-force-resolutions": "0.0.3",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "~3.4.3"
  },
  "description": "An Ionic project",
  "cordova": {
    "plugins": {
      "cordova-plugin-whitelist": {},
      "cordova-plugin-statusbar": {},
      "cordova-plugin-device": {},
      "cordova-plugin-splashscreen": {},
      "cordova-plugin-ionic-webview": {
        "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+"
      },
      "cordova-plugin-ionic-keyboard": {},
      "cordova-plugin-googlemaps": {},
      "cordova-plugin-file": {},
      "cordova-plugin-filepath": {},
      "cordova-plugin-applist2": {},
      "cordova-plugin-app-launcher": {},
      "cordova-plugin-inappbrowser": {},
      "cordova-plugin-media": {}
    },
    "platforms": [
      "browser",
      "android"
    ]
  }
}

routing.module.ts

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)},
  { path: 'media', loadChildren: () => import('./media/media.module').then( m => m.MediaModule)},
  { path: 'apps', loadChildren: () => import('./apps/apps.module').then( m => m.AppsModule)},
  {
    path: 'test',
    loadChildren: () => import('./test/test.module').then( m => m.TestPageModule)
  },
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

and for example AppsRoutingModule:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AppsPage } from './apps.page';


const routes: Routes = [
  {
    path: '',
    component: AppsPage
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class AppsRoutingModule { }

I have noticed that when eg. /home page is the first loaded page and then i switched to /apps and switched back to /home, the home is not initializing multiple times, but when i switch back again to /apps, apps are initializing again.

In AppModule I have provider Routes strategy like that:

    providers: [
        StatusBar,
        SplashScreen,
        {provide: RouteReuseStrategy, useClass: IonicRouteStrategy},
    ],

Does anyone have an idea what’s going wrong?

i have the same problem too with very similar settings
app.routing

const routes: Routes = [
  {
    path: 'home',
    loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
  },
  {
    path: 'news',
    loadChildren: () => import('./news/news.module').then( m => m.NewsPageModule)
  },
  {
    path: 'settings',
    loadChildren: () => import('./settings/settings.module').then( m => m.SettingsPageModule)
  },
  {
    path: 'notifications',
    loadChildren: () => import('./notifications/notifications.module').then( m => m.NotificationsPageModule)
  },
  {
    path: 'address-book',
    loadChildren: () => import('./address-book/address-book.module').then( m => m.AddressBookPageModule)
  },
  {
    path: 'profile',
    loadChildren: () => import('./profile/profile.module').then( m => m.ProfilePageModule)
  },
  {
    path: 'useful-links',
    loadChildren: () => import('./useful-links/useful-links.module').then( m => m.UsefulLinksPageModule)
  },
  {
    path: 'union-news',
    loadChildren: () => import('./news-union/news-union.module').then( m => m.NewsUnionPageModule)
  }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})

xxx.page

const routes: Routes = [
  {
    path: '',
    component: AddressBookPage
  },
  {
    path: 'detail/:code',
    component: AddressBookDetailComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})

package.json

 "private": true,
  "dependencies": {
    "@angular/common": "^9.1.12",
    "@angular/core": "^9.1.12",
    "@angular/forms": "^9.1.12",
    "@angular/platform-browser": "^9.1.12",
    "@angular/platform-browser-dynamic": "^9.1.12",
    "@angular/router": "^9.1.12",
    "@capacitor/android": "^2.4.0",
    "@capacitor/core": "2.3.0",
    "@capacitor/ios": "^2.4.0",
    "@ionic-enterprise/auth": "^3.1.4",
    "@ionic-enterprise/identity-vault": "^4.2.6",
    "@ionic-enterprise/offline-storage": "^2.0.2",
    "@ionic-native/app-version": "^5.28.0",
    "@ionic-native/camera": "^5.28.0",
    "@ionic-native/core": "^5.28.0",
    "@ionic-native/device": "^5.28.0",
    "@ionic-native/file": "^5.28.0",
    "@ionic-native/file-opener": "^5.28.0",
    "@ionic-native/file-transfer": "^5.28.0",
    "@ionic-native/http": "^5.28.0",
    "@ionic-native/in-app-browser": "^5.28.0",
    "@ionic-native/network": "^5.28.0",
    "@ionic-native/pin-check": "^5.28.0",
    "@ionic-native/splash-screen": "^5.28.0",
    "@ionic-native/status-bar": "^5.28.0",
    "@ionic/angular": "^5.3.2",
    "@ngrx/effects": "^9.2.0",
    "@ngrx/store": "^9.2.0",
    "@ngrx/store-devtools": "^9.2.0",
    "@ngx-translate/core": "^12.1.2",
    "@ngx-translate/http-loader": "^5.0.0",
    "@openapitools/openapi-generator-cli": "^1.0.15-5.0.0-beta",
    "@sentry/angular": "^5.25.0",
    "@sentry/tracing": "^5.25.0",
    "cordova-plugin-advanced-http": "^3.0.1",
    "cordova-plugin-app-version": "^0.1.9",
    "cordova-plugin-camera": "^4.1.0",
    "cordova-plugin-device": "^2.0.3",
    "cordova-plugin-file": "^6.0.2",
    "cordova-plugin-file-opener2": "^3.0.4",
    "cordova-plugin-file-transfer": "^1.7.1",
    "cordova-plugin-inappbrowser": "^4.0.0",
    "cordova-plugin-iroot": "^2.0.2",
    "cordova-plugin-network-information": "^2.0.2",
    "cordova-plugin-pincheck": "0.0.6",
    "cordova-plugin-whitelist": "^1.3.4",
    "jetifier": "^1.6.6",
    "moment": "^2.27.0",
    "ngx-webstorage": "^5.0.0",
    "rxjs": "~6.5.1",
    "tslib": "^1.10.0",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.901.12",
    "@angular/cli": "^9.1.12",
    "@angular/compiler": "^9.1.12",
    "@angular/compiler-cli": "^9.1.12",
    "@angular/language-service": "^9.1.12",
    "@capacitor/cli": "2.3.0",
    "@ionic/angular-toolkit": "^2.3.3",
    "@types/jasmine": "^3.5.14",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.12.55",
    "@types/websql": "^0.0.27",
    "codelyzer": "^6.0.0",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~2.1.0",
    "karma-jasmine": "~3.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",
    "protractor": "~5.4.3",
    "ts-node": "~8.3.0",
    "tslint": "^6.1.3",
    "typescript": "~3.8.3"
  },

app.module

@NgModule({
    declarations: [AppComponent],
    entryComponents: [],
    imports: [
        BrowserModule,
        HttpClientModule,
        IonicModule.forRoot(
            {
                swipeBackEnabled: false,
                backButtonIcon: 'arrow-back-outline',
                refreshingIcon: 'refresh-outline',
            }),
        CommonModule,
        AppRoutingModule,
        CoreModule,
        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [HttpClient]
            }
        }),
        StoreModule.forRoot(appReducers),
        StoreDevtoolsModule.instrument({logOnly: environment.production}),
        EffectsModule.forRoot([CacheDataEffect, UserEffect, NotificationsEffect]),
        ApiModule.forRoot(apiConfig)
    ],
    providers: [
        HttpClient,
        StatusBar,
        SplashScreen,
        {provide: RouteReuseStrategy, useClass: IonicRouteStrategy},
        SQLite,
        HTTP,
        {provide: HTTP_INTERCEPTORS, useClass: HttpNativeInterceptor, multi: true},
        AppVersion,
        PinCheck,
        LangService,
        {
            provide: LOCALE_ID,
            deps: [LangService],
            useFactory: (langService) => langService.getLang()
        },
        File,
        FileTransfer,
        FileOpener,
        Camera,
        InAppBrowser,
        Network
    ],
    bootstrap: [AppComponent]
})

So as soon as you change the root Page of the NavController, the previous one is destroyed. Navigating back will trigger the onInit again (and that’s what is expected) :blush:

Example:

You have App with SideMenu Navigation, where each Item in the SideMenu is set as a Root Page. Then the previously Page is destroyed. But if you navigate to another Page B from a Page A via navigateForward, Page A is not destroyed. Navigating back, will destroy Page B and trigger ionViewDidEnter on Page A. Another example are Tabs. Pages in Tabs are only created once and if you switch between the Tabs, the Page itself wont init again. BUT: Same thing, if the Tab Page is the Root Page and you change this, all Tabs are destroyed and inited again on new enter

what do you mean by root page?

From your explanation I understand that the stack of pages is kept only on the children of a page.
Confirm?
because I have a second project where routing is managed in the same way and ngOninit is called only the first time the page is accessed

The Root Page is the one, you set via navigateRoot. On App Startup it is the one you define via routing (if you do so)

I don’t use rootPage as navController is deprecated in ionic 5.
I’m sorry, but I still don’t understand why going from page A to page b the previous one is destroyed. I expect the stack to be maintained anyway.
Otherwise from what I understand I should use children in the definition of the routes

Where did you read that?

Feel free to ignore this interruption and carry on your conversation, but I strongly believe app code should not care about any of this. The timing and manner of component lifecycle management is not an app issue - it’s a framework issue, and the framework should have the freedom to change this at will, without causing any disturbance to app code.

It doesn’t even have to (and probably doesn’t, in real life) be consistent from environment to environment. A device with lots of available resources could intelligently preload components even before they’re used, and cache them aggressively. One without might have to reinitialize pages constantly.

All you should rely on is that creation lifecycle events won’t happen twice in a row without an intervening destruction one. Don’t use lifecycle events to manage business logic.