How to change page from service (@Injectable())

Hi

Is it possiable to change page from an injectable service?
I tried @ViewChild(Nav) nav and nav is undefined
I alos tried this.nav = app.getComponent('nav') by injecting IonicApp to the service but again nav and app are undefined

I looks like every effort to get the NavController to a service fails…
I saw some issues about that but none of them helped me.

anyone?

2 Likes

It is certainly possible to change page from an injected service but this may not be the best idea. Services are generally used to work with data and shouldn’t change the DOM directly, and probably shouldn’t cause navigation either. If however you have a good use case for this, it is possible.

import { Injectable } from 'angular2/core';
import { Router } from 'angular2/router';

@Injectable()
export class AuditDataService {
  constructor(private router: Router) {}

  goToTheHomeRoute() {
      this.router.navigate(['Home']);
  }
}

If the goToTheHomeRoute function is called the app will navigate to the home route. You must provide the route name, not path.

this is the usecase:
For every failed http request I tried to refresh the user’s access token with the refresh token he got in the login.
If for some reason the refresh token fails i want to throw him back to the login screen…

It seams to me that its a bit of an overhead to wrap every http request in the pages with a navigation to the login screen
You think this is a wrong case?

by the way? is there a way to give to a @Page a name?

2 Likes

Seems like a good enough use case to me.

Regarding the page name piece that I have to say I am not sure of. My example was based on an Angular 2 router with route names set up rather than using Ionic 2’s navigation, so, sorry, I probably did not give you the best example. :cold_sweat:

I should have looked at Ionic specifically rather than Angular, that’s my bad. I’ll try to get back to you with a better example if someone else doesn’t beat me to it.

1 Like

So, this is a really good question. From my understanding of the documentation, you cannot inject a NavController into a service. Ionic docs say:

Injecting NavController

Injecting NavController will always get you an instance of the nearest NavController, regardless of whether it is a Tab or a Nav.

Behind the scenes, when Ionic instantiates a new NavController, it creates an injector with NavController bound to that instance (usually either a Nav or Tab) and adds the injector to its own providers. For more information on providers and dependency injection, see Providers and DI.

Unfortunately it looks like all links within the docs are currently broken so I’m not sure what ‘Providers and DI’ would have lead to. But it seems Ionic gives you what it believes to be the proper instance of a NavController for you whenever you are in a @Page, so rather than instantiating a NavController like you normally would providers: [NavController], Ionic handles this for you and you simply inject it in your constructor.

Which leaves the question…how would you do something like this? I’d be interested in hearing what @mhartington or @brandyshea or some other Ionic team member has to say about this.

But you can pass this service like argument. However, it is bad practice, as you said about it above.

Ya, I mean, I can think of a couple hacky ways it might be possible but they all seem scary. Hopefully someone from Ionic can give us an idea.

? pass service like argument it is best way.

@Injectable()
export class AuditDataService {
  constructor(private router: Router) {}

  public checkToken(nav: NavController): void {
      if (!token.isValid) {
        nav.rootPage = LoginPage;
    }
  }
}

FWIW, here’s how I handled this situation. I have a dispatch page with a blank template, and a service that tracks the state of user authentication. Excerpted bits:

export class AuthTracker {
  loginNotifier: Subject<boolean>;
}

@Page({
  template: ``
})
@Injectable()
export class DispatchPage {
  constructor(nav: NavController, authTracker: AuthTracker) {
     let logsub = (loggedin) => {
      if (loggedin) {
        nav.setRoot(TabsPage);
      } else {
        nav.setRoot(LoginPage);
      }
    };
    authTracker.loginNotifier.subscribe(logsub);
}

The LoginPage also injects the AuthTracker and after successful login, calls next(true) on its loginNotifier. Similarly the class that handles logging out calls next(false). TabsPage is the root of the ordinary application, so it gets swapped in as the main root whenever the user is logged in, and LoginPage gets swapped in when the user logs out. DispatchPage is the root of the app.

1 Like

This really isn’t the same issue. This keeps navigation in the page. The question was how do you navigate from within a service. That way if you have a service that makes 10 different api calls, and that service is used on 10 different pages, regardless of what page you are in if one of those api calls returns a 401 you get redirected to a login screen. Unless I’m not understanding your code.

2 Likes

There would have to be an error handler in the service that calls next(false) on the loginNotifier in the case of the 401. Wouldn’t that achieve what OP wants?

1 Like

Ah, so AuthTracker would also be used in the service as well. In which case I get what you mean, sorry I didn’t follow that part in the original code example but I get you now.

But here’s my other problem though, at this point you are more or less doing the same thing @xr0master suggested, albeit in a different way. This might work but, it still concerns me with this whole NavController thing. Because now regardless of where you are in the app you are using the NavController Ionic decided to instantiate in the DispatchPage which may or may not be the same NavController instantiated on the page you are on.

This is where it’s not clear to me if this is okay or not, and why I wanted to hear from someone at Ionic. Because Ionic is managing those NavControllers…so I don’t know

Hmm. In my situation, I don’t think that’s a problem, because the DispatchPage is the root page of the application. It’s the main gatekeeper that decides whether we go to the app (user is logged in), or we go to the login page (user is not logged in). In reality, there’s a third possibility of “user is not registered, go to the signup page”, but that’s probably not important here.

Even if there is some other NavController that’s controlling navigation within the mechanics of the various pages of the authenticated zone of the app (and I don’t know whether or not that’s the case), I think I always want the one associated with the DispatchPage handling login/logout/register/unregister events.

Ya I think you are right, the whole NavController thing just seems like magic…which is both good and bad. Like, is it possible for that page to ever be destroyed? Probably not since it’s the root page like you say. Again, I think you are correct, it just weirds me out that we have this magic dependency injection that can’t be injected into services and we then have to pass around that injected object. I don’t think anything like that exists in vanilla Angular 2, but idk.

1 Like

My mental image of it is something like this. Imagine that we have my Dispatch/Login/Tabs structure, so Dispatch can set root to either Login or Tabs. Let’s say Tabs has three tabs A/B/C, and each of those has subtabs 1/2/3.

We navigate to A2 and then to B1. There are now three NavControllers: one handling the Dispatch-to-Tabs line, one going Tabs-A-A2, and another (active) going Tabs-B-B1.

Now we click on the A tab. In order to remember that when we were on A, we were on A2, we reactivate the A line NavController, that knows it was pointing at A2.

In summary, I think the reason multiple NavControllers need to exist is to remember navigation paths that are currently dormant due to changes made higher up in the food chain, and in the particular situation involved in this discussion, we always want the NavController involved with the choice of “do we enter the app or require login?” to be the one associated with the dispatch page.

Of course, somebody on the dev team will probably be along to point and laugh at me any moment now, but live and learn.

2 Likes

Or they may say you are correct. What you are saying does make sense, it would just be nice to have some clarification.

Use Rx/Js Events to solve this problem

Setup your Service to send and receive events

mport { Injectable } from '@angular/core';
import {ServerConfig} from "./ServerConfig";
import {Http, Response} from "@angular/http";
import {Observable, Subject} from "rxjs/Rx";
import {HttpOptions} from "./HttpOptions";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { ToastController} from 'ionic-angular';
import {Landing} from '../pages/landing/landing'; //Your landing page

@Injectable()
export class CentralService {
  private baseUrl:string;

  public controller:any;

  private subject:Subject<any> = new Subject<any>();

  constructor(private http: Http, private toast:ToastController) { }
    build(suffix:string, controller:any):CentralService{

        let cetUrl = (new ServerConfig(false)).url + "/user/";
        this.controller = controller;
        this.baseUrl = cetUrl + suffix;

       return this;
     }
  mySimpleHttpRequest():Observable<any>{
         return this.http.get(this.baseUrl + "/all", {headers: (new HttpOptions()).headers})
                 .map((res:Response) => {

                    return res.json();
                })
               .catch((error) => this.error(error))
   }


  gotoRoot(){
    this.subject.next("gotoRoot");
  }

  onRoot():Observable<any>{
    return this.subject.asObservable();
  }

  public error(err:any):any{
    console.log(err)
    if(err.status == 401){
      
      this.toast.create({
          message: 'Session Expired. Please Log In',
          duration: 3000,
          position: 'top'
        }).present();
      
      this.gotoRoot();  //Trigger the event 

      return Observable.throw(err.json() || "Server Error");
    }
    return Observable.throw(err.json() || "Server Error");
  }
}

Then in your controller/component

  ionViewDidLoad() {
 
    /**
    * Listen to the event and fire the action whenever the event is received!
    */

      this.centralService.onRoot().subscribe(res =>{
        this.navCtrl.setRoot(Landing)
      })

  }

Hi all did we got any final answer to do so…

Not that you have to have any reason to care about my opinion, but it’s firmly that the topic of this thread is a horrible idea and should not be attempted.

1 Like

@rapropos i got you… now i just tring to handle my exception on a service layer method and then wanna paas it to another service layer … is it possible anyway ???