ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
51.03k stars 13.51k forks source link

Ionic router. OnInit working only once #17853

Open traziusbiztest opened 5 years ago

traziusbiztest commented 5 years ago

Bug Report

Ionic version: [x] 4.1.0

Current behavior: Ionic router run OnInit only first time

Expected behavior: Run OnInit each time

I have an app with Angular. It is a simple list of items, after clicking on an item opened item details page. Two routes:

const routes: Routes = [
  {
    path: 'index',
    component: IndexComponent
  },
  {
    path: 'item/:id',
    component: ItemDetailsComponent
  }
];

I use ItemsService -> get/save items.

export class IndexComponent implements OnInit {
  public items: Array<Item>;

  constructor(private itemsService: ItemsService) {
  }

  ngOnInit() {
    this.itemsService.getItems()
    .subscribe(data => this.items = data)
  }
}
export class ItemDetailsComponent implements OnInit {
  public item: Item;

  constructor(private route: ActivatedRoute,
              private itemsService: ItemsService) {
  }

  ngOnInit() {
    const id = this.route.snapshot.params['id'];
    this.itemsService.getItem(id)
    .subscribe(data => this.item = data)
  }
}

In Angular application, OnInit lifecycle event working each time when I open these pages. And it is fine. I should edit an item on ItemDetailsComponent, and after clicking back to IndexComponent I see new information.

I start an Ionic project. Changed router-outlet to ion-router-outlet Same structure, same routing. And now:

traziusbiztest commented 5 years ago

I tried to delete

  {
    provide: RouteReuseStrategy,
    useClass: IonicRouteStrategy
  }

from app.module

But OnInit still not working

traziusbiztest commented 5 years ago

https://gyazo.com/6afec1e7936e1254b41009ab942a2d3f

How I can disable this? I need only one component, not stack

traziusbiztest commented 5 years ago

If I use custom RouteReuseStrategy I can't use route animations

digaus commented 5 years ago

Just subscribe to the params...

 this.route.params
            .subscribe((params: Params) => {
                console.log(params['id']);
});
traziusbiztest commented 5 years ago

@digaus, This is not working, because of an old component in stack,

liamdebeasi commented 5 years ago

Hi @traziusbiztest,

Thanks for the issue! I can confirm this is a bug. By default, we cache the component when navigating (whereas a regular Angular component destroys it and re-creates it when going back). We have a fix in the works that should resolve issues where routing isn't working after the first time, params aren't being updated, etc.

Work on the fix is well underway, and I hope to have more info to share soon!

Thanks for using Ionic!

traziusbiztest commented 5 years ago

Temporary solution? How to destroy an old component in stack?

liamdebeasi commented 5 years ago

Hi there,

If you are trying to run code every time a particular page enters (or re-enters the view), you can use the ionViewDidEnter lifecycle hook. This hook is "fired when the component being routed to has animated in": https://ionicframework.com/docs/api/router-outlet

If you are trying to respond to changes in data, you could subscribe to the data via an Observable as @digaus suggested, or you can create an injectable that holds the data. With the injectable, you can write a method that serves up the data you need.

For help on implementing either of these ideas, feel free to post on our forums or our slack.

Thanks!

KillerCodeMonkey commented 5 years ago

i totally agree with @liamdebeasi

ngOnInit is only called, when the angular component gets initialised, which is not the case for ionic routing. the previous pages are cached and only disconnected from the change detection. The ionic component lifecycle hooks and the angular router events/observables should fulfill your needs.

It is like ionic navigation is working. Maybe it should be better documentated or clearified in the docs.

tobika commented 5 years ago

I also stumbled over this bug. I changed the lifecycle events to the ionic ones which works well on components that are accessed via the router (let's call them pages). But on components that are on this page the ionViewWillLeave doesn't seem to be called. So the component that is on a page is not notified if the parent component is going into the cache.

Because of this I cannot unsubscribe an observable that I passed to the sub-component.

Hope this makes sense, I'll try to notify the component via an input that is called on ionViewWillLeave of the parent component.

TMInnovations commented 5 years ago

I also detected the same behavior when I tried to follow the tutorial of Maximilian Schwarzmüller @ https://www.youtube.com/watch?v=r2ga-iXS5i4&t=2h57m53s For me, the tutorial doesn't work anymore when it's about trying to delete positions of the list like in https://www.youtube.com/watch?v=r2ga-iXS5i4&t=3h17m0s and change back to the view which lists all positions.

I tried ionViewDidEnterand it worked well for me. Is there any better idea?

Juanperezc commented 5 years ago

Same bug here.

irealyze commented 5 years ago

Hi, I tried to work around with this by using ionic events. I had the same problem when a route is initialized but requires re-authentication(when it tries to retrieve data(by ngOnInit) but instead get a 401 error in return because of expired token(verification happens on my backend)) so navigating to login route. After a successful login, navigate back to the previous URL to retrieve the data again. which result to the OnInit never gets called again after initialization.

I tried to use router NavigationEnd but I encountered problems, like it is firing multiple times. so i decided to use the ionic events.

I just specify the topic on publish(I used the router url as topics) and for every route subscription. so if an event.subscribe hits the right topic. It can execute a certain function(call ngOnInit again).

https://ionicframework.com/docs/v3/api/util/Events/

hope someone might find this helpful. sorry its my first time to post comment.

thinkdj commented 5 years ago

Please work on this issue on priority. Many are facing this issue and will start creating workarounds and/or assume ngOnInit will be loaded once and write some logic. When this behavior changes, it will cause issues in the future.

There should be a way to not cache views at all!

ramzikorkor commented 5 years ago

It seems that ngOnInit() will fire so long as some parameters change. Using ionDidViewEnter(), doesn't work very well because it fires after the components have been generated, causing errors.

So what I have used, is ugly, but it works. Every time I navigate to a page that I have previously visited, I append a random number (uuid to insure uniqueness) to the parameters. This causes ngOnInit() to fire every time a page is visited.

No perfect, not pretty, but practical.

tomriddle1234 commented 5 years ago

5 months still open?

Tauqeer1 commented 5 years ago

Is this bug has fixed ?

thinkdj commented 5 years ago

@Tauqeer1 This has been fixed in the newer versions of Ionic.

Zeehond99 commented 5 years ago

It's not fixed, still facing this issue

thinkdj commented 5 years ago

It's not fixed, still facing this issue

@Papabeer04 Which version ? Fresh install or an upgrade using package.json ?

Zeehond99 commented 5 years ago

It seems that ngOnInit() will fire so long as some parameters change. Using ionDidViewEnter(), doesn't work very well because it fires after the components have been generated, causing errors.

So what I have used, is ugly, but it works. Every time I navigate to a page that I have previously visited, I append a random number (uuid to insure uniqueness) to the parameters. This causes ngOnInit() to fire every time a page is visited.

No perfect, not pretty, but practical.

I'm getting: ExpressionChangedAfterItHasBeenCheckedError

Can you show an example of how you did it?

Zeehond99 commented 5 years ago

It's not fixed, still facing this issue

@Papabeer04 Which version ? Fresh install or an upgrade using package.json ?

5.2.4

Zeehond99 commented 5 years ago

My problem remains with a modal, when I open the modal and close it and go to another page. Then go back to the page and open the modal again ionViewWillEneter and ionViewDidEnter don't work.

ionViewWillEnter fixed my normal pages at the moment.

kamvir commented 5 years ago

I used ionViewWillEnter() instead of ngOnInit(). It works,

lhtrang commented 5 years ago

I did some investigations. Hope it helps Ionic version @ionic/angular 4.11.1

Actual result I have 2 pages: Welcome, Login and I just navigate forward, don't have any back navigation. Scenario 1

  1. Start app from Welcome page.
  2. Navigate to Login page
  3. Continue navigation to Welcome page, after that navigate to Login page.
  4. From log I could see Login page OnInit event fire 2 times. Work like a champ, I guess because only Welcome is in stack.

Scenario 2

  1. Start app from Login page.
  2. Navigate to Welcome page.
  3. Continue navigation to Login page, after that navigate to Welcome page.
  4. From log I only see Login page OnInit event fire 1 time. Guess right now it is in stack now. can not re-initiate.

Expected Result From log I should see 2 lines of log for Login page OnInit event.

Thanks for improving this framework!


Guys. Please don't say I use ionViewWillEnter and it works. What does temporary solution mean for you? What @liamdebeasi suggest is using ionViewWillEnter when "run code every time a particular page enters" and it is acceptable solution for this case only. How about the other cases? You will get temporary solution and use another temporary solution because this temporary solution make. You might ask why. I will show you how Ionic ionViewWillEnter and Angular ngOnInit different in log. Ionic-Angular already different when it fires component events.

Angular LoginPageComponent constructor ngOnInit ngDoCheck ngAfterContentInit ngAfterContentChecked ngAfterViewInit ngAfterViewChecked

Ionic-Angular LoginPageComponent constructor ngOnInit ngDoCheck ngAfterContentInit ngAfterContentChecked ngAfterViewInit ngAfterViewChecked ngDoCheck ngAfterContentChecked ngAfterViewChecked ngDoCheck ngAfterContentChecked ngAfterViewChecked ionViewWillEnter ngDoCheck ngAfterContentChecked ngAfterViewChecked ionViewDidEnter ngDoCheck ngAfterContentChecked ngAfterViewChecked ngDoCheck ngAfterContentChecked ngAfterViewChecked

m4szl4g commented 4 years ago

My problem remains with a modal, when I open the modal and close it and go to another page. Then go back to the page and open the modal again ionViewWillEneter and ionViewDidEnter don't work.

ionViewWillEnter fixed my normal pages at the moment.

@Papabeer04 , I'm facing with the same issue just as you wrote. Did you able to fix the issue?

When will ionic team fix this issue? As far as I see it's not a new one...

mozib commented 4 years ago

Dear All, @traziusbiztest @liamdebeasi @Papabeer04 @kamvir @thinkdj

I have tried all the above suggestions but no luck for me. Please let me know what I need to do as I am also facing same problem.

Why ngOnInit() works only once when I come back to home page the data not displaying. :(

Please help me. Thanks in advance.

Mozib Khan

Zeehond99 commented 4 years ago

For me the problem was actually in my service. I followed the course of Simon Grimm but he makes a mistake.

(Using firebase)

service.ts :

 constructor(private afs: AngularFirestore) {
    this.ideaCollection = this.afs.collection<Idea>(ideas);
    this.ideas = this.ideaCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }

  getIdeas(): Observable<Idea[]> {
    return this.ideas;
  }

Should be:

 constructor(private afs: AngularFirestore) {
    this.ideaCollection = this.afs.collection<Idea>('ideas');

  }

  getIdeas(): Observable<Idea[]> {
    return this.ideas = this.ideaCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }
mozib commented 4 years ago

For me the problem was actually in my service. I followed the course of Simon Grimm but he makes a mistake.

(Using firebase)

service.ts :

 constructor(private afs: AngularFirestore) {
    this.ideaCollection = this.afs.collection<Idea>(ideas);
    this.ideas = this.ideaCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }

  getIdeas(): Observable<Idea[]> {
    return this.ideas;
  }

Should be:

 constructor(private afs: AngularFirestore) {
    this.ideaCollection = this.afs.collection<Idea>('ideas');

  }

  getIdeas(): Observable<Idea[]> {
    return this.ideas = this.ideaCollection.snapshotChanges().pipe(
      map(actions => {
        return actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data };
        });
      })
    );
  }

Wow! you are great it works for me.

Thank you so much @Papabeer04

Please get in touch via my skype (chaton_skype)

Thanks,

Mozib

thebest2019 commented 4 years ago

Try this:

constructor(
    private zone: NgZone
  ) {}

this.zone.run(() => {
      this.router.navigateByUrl('/login', { skipLocationChange: true });
 });
BorntraegerMarc commented 4 years ago

This is happening because ionic router caches pages. If the page is cached only ionViewWillEnter will be called.

But if a page wasn't cached then ngOnInit will be called every time

ipsjolly commented 4 years ago

Still facing issue with @ionic/cli version v6.4.1

(_) (_) | |/ | '_ | |/ _| | | () | | | | | ( |_|_/|| |||\| CLI 6.4.1

anasvn commented 4 years ago

Any updates ? Ionic 5 still having this issue.

GabrielFerraz commented 4 years ago

Hi, I tried to work around with this by using ionic events. I had the same problem when a route is initialized but requires re-authentication(when it tries to retrieve data(by ngOnInit) but instead get a 401 error in return because of expired token(verification happens on my backend)) so navigating to login route. After a successful login, navigate back to the previous URL to retrieve the data again. which result to the OnInit never gets called again after initialization.

I tried to use router NavigationEnd but I encountered problems, like it is firing multiple times. so i decided to use the ionic events.

I just specify the topic on publish(I used the router url as topics) and for every route subscription. so if an event.subscribe hits the right topic. It can execute a certain function(call ngOnInit again).

https://ionicframework.com/docs/v3/api/util/Events/

hope someone might find this helpful. sorry its my first time to post comment.

I had the same problem as @irealyze on Ionic 5

anRoswell commented 4 years ago

ionic 5 still the problem persist

gischy commented 4 years ago

i also had trouble with this issue (refactored my navigation from tabs to pages thinking these would solve the on init issue), but it seems to be a general issue where a clear workaround/guidance for exists

the current docs of ionic tell how to deal with it:

https://ionicframework.com/docs/angular/lifecycle#how-ionic-handles-the-life-of-a-page https://ionicframework.com/docs/angular/lifecycle#guidance-for-each-life-cycle-method

guidance says:

ramseyfeng commented 3 years ago

Hi @traziusbiztest,

Thanks for the issue! I can confirm this is a bug. By default, we cache the component when navigating (whereas a regular Angular component destroys it and re-creates it when going back). We have a fix in the works that should resolve issues where routing isn't working after the first time, params aren't being updated, etc.

Work on the fix is well underway, and I hope to have more info to share soon!

Thanks for using Ionic!

What's the progress of this issue? Do we have plan to fix it? Or is there an official suggestion how we should handle with it.

LAccastello commented 3 years ago

Like @kamvir said I used ionViewWillEnter() instead of ngOnInit() and it works.

Domvel commented 3 years ago

Note that this is not a pages only problem. The Ionic Life Cylce events like ionViewDidEnter etc are great and should be used in pages. But it does not work with other components. So if you have a standalone component that expects ngOnInit to be called every time you view a page, it won't work. Is there no workaround? Different routing strategy etc. In best case we need an event which is called only once on page enter (but not on nav back), like the current behavior. And an event called every time the page gets in view. An event for all components. Not only pages. Or a mode which destroys the page on forward navigation. Maybe just a config question. Root navigation may also help. But will not work with back navigation I guess. Or listen to router.events. Or you can set the attr. replaceUrl="true" on the element with routerLink.

darkguy2008 commented 3 years ago

I've managed to fix the issue this way:

On page components, I can use ionViewDidEnter, ionViewWillEnter, etc.

I discovered that components don't call those events, but they are always recreated everytime Ionic enters the view, therefore, for components, OnInit() will be called everytime, and for pages, it's being called just once (because the page component is created only once, duh).

If you use it like this, you won't have issues.

infacto commented 3 years ago

ionViewWillEnter etc does not work on components. Only for pages. Is there no solution to fully load the page again on back navigation? Or another solution to have the same behavior like in Ionic 3.

marcinowski commented 3 years ago

This problem took me by surprise but tbh jokes on me for not reading the documentation which clearly states how it works https://ionicframework.com/docs/angular/lifecycle#how-ionic-handles-the-life-of-a-page Coming from angular it was a bit of a shock that ngOnInit wasn't called all the time though but fair enough, given all that's said in the docs I don't see that as an issue anymore.

svmashok commented 3 years ago

Hi all,

we (Myself and @veeravetrivel) have tried with the following workaround and its working fine for us.

He already raised a new ticket for this. But unfortunately, they closed it without any strong solution for that and just asked us to follow ionic life cycle hooks

https://github.com/ionic-team/ionic-framework/issues/23765

So, we tried to find a workaround and its working fine for us. Hopefully it will also work for others who are stucked up to reinstantiate the already routed angular component.

The following workaround will remove the previous route component from the pages stack and place only the routed current component in the stack.

And called the previous route component's ngOnDestroy. And called the current route component's constructor and ngOnInit.

import { NavController } from '@ionic/angular';

constructor(
    public navController: NavController
  ) {

}

navigatePage1() {
          this.navController.pop();
          this.navController.navigateBack('/page1');
}

navigatePage2() {
      this.navController.pop();
      this.navController.navigateBack('/page2');
}

Note: We don't face any side effects using this implementation and its working fine with respect to angular life cycle hooks. If someone facing any issue then, only admin can help us exactly the internal behavior or side effects with respect to ionic life cycle hooks to use this APIs

znotdead commented 3 years ago

I've ended up with custom RouteStrategy where shouldReuseRoute is either configured via data in routing to return false or not. Or if you need always have all life cycles on all pages than shouldReuseRoute need to return false. Looks like this worked for us after juggling with Pages with Tabs and Components in them that need to be reinited.

devIbtihadj commented 1 year ago

what about in the new version please???

Mateus-Felipe-Ribeiro commented 1 year ago

I faced the bug today, 2019 - 2023, and the bug still here, any solution for this?

reza-iranmanesh commented 1 year ago

I have to vent a bit first: some of the decisions by Ionic team has killed the soul of Angular IMO. Those beautiful self contained components are no more.

My current two workarounds to access router snapshot is either through a resolver, or to always navigateRoot to a component (forcing Ionic to create the component again and not use a cached version).

One of the interesting things you will face with Ionic if you are new to it (not exactly related to your issue here), is that sometimes it decides to call the Ionic life cycle hooks and sometimes it doesn't; so don't trust them all the time either (from what I remember if it's a Page or Component that is called by router then Ionic lifecycle hooks are called on it, if it is a component embedded in a top component then they are not)

LennonReid commented 1 year ago

I faced the same issue when the authentication router guard navigated to the login page and then back to the home page, resulting in neither the ngOnInit() nor ionViewWillEnter() methods being executed. Here is my solution: On the page where you want to call ngOnInit() or ionViewWillEnter() more times:


import { ActivatedRoute, Router, Event, RouterModule, RouterEvent, NavigationEnd } from '@angular/router';
    // In constructor
    const currentUrl = this.router.url;
    this.router.events
      .pipe(
        filter((e: Event | RouterEvent): e is RouterEvent => e instanceof RouterEvent),
        filter(e => e.url === currentUrl)
      )
      .subscribe(
        (event: RouterEvent) => {
          if (event instanceof NavigationEnd) {
            console.log(event);
            // Do something
          }
        });

This code listens for router event changes and refreshes the current component if the navigation URL matches the current component route URL

LennonReid commented 1 year ago

Update: Using this.navController.navigateRoot() instead of this.navController.navigateForward() or this.router.navigate resolved my issue. Additionally, note that the ionViewWillEnter lifecycle will run multiple times if the previous page was navigated using this.navController.navigateRoot().

ConnorBrennan commented 6 months ago

Still facing this issue intermittently in Ionic 7, five years after this issue was originally filed!

Observables are being refreshed for pages that I didn't initially load in on and first accessed via the router, but whatever page I started on will never refresh!