[SOLVED] Ionic navController pop with params

do not understand what you are asking?

Hello @aaronksaunders & others –

I’ve been working through a similar problem as you’ve solved here, namely, I believe I need to pass a value back to an earlier screen in my Ionic (progressive web) application’s navigation stack.

Specifically, I am handling a case where a user of my product is going back to an earlier page which may make a POST request to my API server. I have a CSRF token which was GET’ed during the initial authentication with my API and needs to be available on every screen that can make POST requests. This is easy when moving forward, since I pass the token value along via navParams when pushing items onto the stack. Without this value, my API cannot accept the request.

SO: when popping items off the stack, does their original scope (the various “this.varName=…” from the original time they were inserted still exist? If so, I don’t need to pass anything backwards - the CSRF value will remain accessible for inclusion in any new POSTs generated by the page. If not, would you mind explaining how exactly the code you posted back in July '16 works? I’m not following it…

Many thanks and keep up the awesome work,
Andrew

One item of explanation to assist here – the reason I’m using the this.navController.pop() method here is because in the browser on my target devices (iOS and Android phones), users frequently tap the “back” button in their browser (or the hardware reverse-arrow one, for android) which seems to automatically trigger a backward navigation action.

In order to keep my application from fritzing out (losing its CSRF token) I tend to remove the built-in back button from Ionic2’s navigation bar and include a button with my own code running (the pop()). An earlier attempt to simply push on the earlier view with the correct data had the downside that users employing their browser’s back button ended up seeing earlier screens in the navigation stack with stale data on it… not to mention mucking up my data flows.

If you have any suggestion that would make this easier, I’d welcome that as well. I’m hoping for now that each page in the navigation stack simply includes the data that was on it at the time (when initially push()'ed) so the CSRF token value will continue to exist.

Thanks,
Andrew

This strategy of passing authentication tokens in NavParams seems extremely brittle to me. I think stashing them in a shared service would be vastly preferable.

Can you explain that idea a bit more? RIght now I have a service which does the GET to my API server to request a new token value which is then passed along as a navParam…

It sounds like I could have the initial request to GET the value from the server could be stored as a variable value within the service I have which does that call - and then as long as that service is attached to any of my pages, I should be able to access it (after the initial call is made)?

Apologies about being pretty new to Angular2… coming from an embedded world where things are a wee bit simpler to visualize.

Thanks,
Andrew

Right. You probably don’t even want to really access it from the pages themselves (although you can do that if you wish). Typically, all the pages care about is “are we authenticated?”.

I use JWT for authentication, and here’s my approach. You should be able to adapt it to your situation.

Sink

This is a service that interacts with ionic-storage to persist credentials across app restarts. If you don’t want to do that, just store the tokens temporarily.

export class Sink {
  private _jwt: string;
  private _authNotifier: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);

  constructor(private _db: Storage) {
    // load from storage
  }

  authenticationNotifier(): Observable<boolean> {
    return this._authNotifier;
  }

  // don't call this until the authentication notifier has fired true
  getJwt(): string {
    return this._jwt;
  }

  setJwt(jwt: string): Promise<void> {
    this._jwt = jwt;
    this._authNotifier.next(!!jwt);
    // save it in storage, return resultant promise
  }
}

AuthedHttp

This is a wrapper for Http that handles adding authorization headers. Mostly fairly boring boilerplate, but it can inject Sink in its constructor, get the authentication token, add it to headers, and use that in requests, all transparently to consumers.

App component

constructor(sink: Sink) {
  sink.authenticationNotifier().subscribe((authed) => {
    if (authed) {
      this.rootPage = AuthedPage;
    } else {
      this.rootPage = UnauthedPage;
    }
  });
}

Now any time anybody anywhere calls setJwt(somethingValid), the root page automatically flips to the authorized home page. If setJwt(null) is called, the page automatically goes to the unauthed page. Logout becomes dead simple; no fooling with navigation controllers at all.

Login page

constructor(private _http: Http, private _sink: Sink, private _nav: NavController) {
}

doLogin(): void {
  this._http.get('url-for-auth-token').subscribe((rsp) => {
    let authtoken = magicallyExtractToken(rsp);
    this._sink.setJwt(authtoken).then(() => {
      this._nav.pop();
    });
  });
}
1 Like

In our use case, we need to be able to apply a specific value to every POST that our Ionic2 application makes to our API (about a half-dozen endpoints). The value is a token which is generated on a GET-only endpoint and can be parsed only by code that has permission via CORS origin controls… so the workflow is:

  • app is loaded
  • user begins interaction, causing the GET /token request to fire
  • token value is returned and stored in app
  • every new action with a POST has a header included whose value is the token body

on the API, the header value is compared with the original value provided… this allows for confirmation that a browser-based caller of the POST endpoints cannot trigger any API actions unless they have CORS access granted. Not really authentication (that’s handled another way), but just CSRF protection.

I think the same general skeleton should work perfectly for you.

export class AuthedHttp {
  constructor(private _sink: Sink, private _http: Http) {
  }

  post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
    let allopts = this._frotzOptions(url, options);
    return this._http.post(url, body, allopts);
  }

  private _frotzOptions(urlo: string | Request, options: RequestOptionsArgs): RequestOptionsArgs {
    if (!options) {
      options = {}
    }
    if (!options.headers) {
      options.headers = new Headers();
    }
    let token = this._sink.getToken();
    // add it to options.headers
    return options;
  }
}
export class TypicalPage {
  constructor(private _ahttp: AuthedHttp) {
  }

  foo() {
    this._ahttp.post('restricted-url', body).subscribe((rsp) => {
    });
  }
}

Not a NavParam in sight.

2 Likes

Cool, thanks! I’ll run some tests locally to see if I can get this integrated without using any annoying navParams.

May G-d bless you man! It worked fine, you are a genius, I hope someday have the knowledge you have. Thanks a lot!

Works like charming. Save my time.Thanks bro

Hi, may i know how did you do it?
i assigned _params to my local variable, however it shows undefined when i use ionViewDidEnter to console the local variable.
Can you help me ?

2 Likes

@aaronksaunders Thanks for solution.

Needed a slight tweak

    let _that = this;
    this.callback = function(_params) {
        return new Promise( (resolve,reject) => { 
                                    console.log('In callback:', _params);
                                    _that.credentials = _params;
                                    resolve(); 
                                                }
                            );
    }

Please don’t emulate the previous post. It packs several antipatterns into a fairly small space.

Great! Thank you so much.

Wow, Sneaky love it!!

I achieved similar functionality using Events listeners using following codes -

MainPage-

import { NavController, Events } from 'ionic-angular';
import { OtherPage } from '../other/other';

export class MainPage{
    constructor(private navCtrl: NavController,
                private events: Events) { }
    
    private pushOtherPage(){
        this.events.subscribe('custom-user-events', (paramsVar) => {
            /// Do stuff with "paramsVar"

            this.events.unsubscribe('custom-user-events'); // unsubscribe this event
        })

        this.navCtrl.push(OtherPage); // Push your "OtherPage"
    }
}

OtherPage-

export class OtherPage {
    /// Under some function
    this.navCtrl.pop().then(() => {
        /// Trigger custom event and pass data to be send back
        this.events.publish('custom-user-events', myCustomParams);
    });
}
4 Likes

NavController’s pop method already returns a promise which is resolved when the transition has completed. The following approach is based on that fact and it is different from the accepted answer, but works in a similar way:

Main page

export class MainPage {
  dataFromOtherPage = null;

  callback = data => {
    this.dataFromOtherPage = data;
    console.log('data received from other page', this.dataFromOtherPage);
  };

  constructor(public navCtrl: NavController) {}

  openOtherPage() {
    this.navCtrl.push('OtherPage', {
      callback: this.callback
    });
  }
}

The other page:

export class OtherPage {
  constructor(public navCtrl: NavController, public navParams: NavParams) {}

  goToPreviousPage() {
    this.navCtrl.pop().then(() => {
      this.navParams.get('callback')('This is some data');
    });
  }
}

8 Likes

Thank you!
But for others, I must say that you need to
bind ‘this’ to the callback function.
Otherwise '‘this’ will be the other page model,
no the expected one.

Store values in storage and use ionViewDidEnter in your previous page

ionViewDidEnter(){
//get params here
}