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

I would expose it as an Observable and subscribe to it where desired. I still think any interaction with the view layer should be done by view layer objects - i.e. pages, not services.

1 Like

Hi Guys,

I had this same problem and i fix that with event

In my service:

@Injectable()
export class HttpClient {
  constructor(
    public events: Events
  ) {}
  ...
  public error(err:any):any{
    if(err.status == 401){
        this.events.publish('session:expired');
    }    
}

And i put a subscriber in my app.component:

@Component({
  selector: 'main-app',
  templateUrl: '../pages/main/main.html'
})
export class MainApp {
  rootPage;
  constructor(
    public events: Events
  ){
    events.subscribe('session:expired', () => {
      this.rootPage = LoginPage;
    });
  }
}

With that i could fix this problem of session expired and put the login page in my app!
I hope it is useful in some way.

2 Likes

In general, I like your approach. A couple of things:

  • Observables > Events
  • While tempting, HttpClient is a bad name choice, because it is going to clash with the new API in Angular 4.3.
1 Like

I think I put something together today that MIGHT be relevant. Seems pretty similar to a couple other posts, but might provide something extra.
In my Component, checking login status.

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { AuthService } from '../../providers/auth-service';
import { NavController, NavParams } from 'ionic-angular';
import { ImagePage } from '../../pages/image-page/image-page';
export interface User {
  email: string,
  emailVerified: boolean,
  uid: string
}
@Component({
  selector: 'page-login-page',
  templateUrl: 'login-page.html',
})

export class LoginPage {
private loginForm: FormGroup;
 userLoggedOut: boolean;
 returnedUser: User;
  constructor( private navCtrl: NavController, private authService: AuthService, private formBuilder: FormBuilder) {

    this.loginForm = this.formBuilder.group({
      email: ['', Validators.compose([Validators.required])],
      password: ['', Validators.compose([Validators.required])],
    })
 
  }

checkAuthState(){
    let authState = this.authService.returnAuthState();
    let condition = authState.subscribe((user) => {
       if (user) {
          this.returnedUser = {
           email: user.email,
           emailVerified: user.emailVerified,
           uid: user.uid
          };
          this.navCtrl.push(ImagePage,{
            user: this.returnedUser
          })
         }
        condition.unsubscribe();
     })
}

My Provider, AuthService, with everything but the example of passing the observable from provider to component.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { AngularFireAuth } from 'angularfire2/auth';
import { AlertController } from 'ionic-angular';

export interface RegisteredUser {
  email: string,
  password: string,
  emailVerified: boolean
}

interface CheckedUser {
  validityCheck: string,
  first_name: string,
  last_name: string,
  email: string,
  password: string
}

@Injectable()
export class AuthService {
 validityValue: boolean;
  registeredUser: RegisteredUser;
  constructor( private alertCtrl: AlertController, private afAuth: AngularFireAuth, public http: Http) {
    
  }

 returnAuthState(){
   let x = this.afAuth.authState;
   return x;
 }
}

Well… the name is only a sample. I don’t copied the code, i typed here only to explain! :slight_smile:
I dont remember what name i used in my project. HttpService maybe.

I agree with your concern…
I use constants to reduce the problem with meaning typos. But all others questions is a problem…
But, is better than use observable in many classes (i dont like that). The event i use only once. I wil think about how i can do better with observable.

Interesting that I happened to get a notification about this thread at the same time I actually had to deal with a similar problem. When I got a 401 error I needed to be able to trigger navigation app wide. Because of the third-party security plugin I was using, I didn’t have much choice and had to trigger navigation from a service. Again I’ll say this is something you should approach with caution, but I think there are valid use cases.

This also only changes the root navigation, which for something like an auth screen is almost always what you want, but this isn’t a universal catch all.

To navigate from a service, you can simply inject App into your service, and from there get the root nav.

this.app.getRootNav().setRoot(LoginComponent);

Again, you shouldn’t just try to blindly use this, and you’ll run a great risk of introducing circular dependency issues that stop your app from running…but in my case I actually needed to do this and there was no real way around it. So…here it is :slight_smile:. Use with caution.

Why couldn’t you expose an authorization notifier in the injectable, subscribe to it in the app component, call next(false) on it whenever you get a 401, and do all the actual nav interaction in the app component?

I could, you’re right, but, I’m giving the simplified version. This plugin could trigger multiple different navigations, not just an invalid token, but it also triggered navigation to different steps of the registration process. Of course I think that’s a fault of this plugin, but I will say that if you have multiple different semi-complex navigation you need to trigger from a service, to me it makes more sense to keep that navigation logic in a relevant place (security.service.ts) instead of in just a base component (app.component).

There are other options too, maybe making a base security component that had the nav logic in there. And overall I agree with you, that’s why I put the “use with caution” warning there. It’s a solution if someone needs it, but in most cases it certainly isn’t the best solution.

1 Like

I’m probably just jaded and cynical, but it seems like once every couple of days I see somebody posting a “thanks for this awesome solution” in response to a comment in a 3-month-old thread that did or at least should have had that warning in it.

I guess if somebody’s hell-bent on taking out a monster loan at the Bank of Techdebt, nothing I say will convince them otherwise.

Found this on google so that’s why I’m posting.
Was trying to build a logout service. Both the menu (from app.component) and the back button on our root page both needed to call this same service (popup a box asking if they really want to log out, call the logout ws and redirect to the login page). Trying to use NavController from the service is where I ran into problems.
I Like the Event solution that @luizadolpho gave. I just have all the code in app.component (where Navigation is at anyways) Set up an extra event wrapper.