Where to put and how to achieve complex Navigational Logic?


#1

So, we have a Navigation Service which schedules the logic which page follows which and if a user needs to be logged in etc, quite complex. And now I want to use `navController.push(page), but from my understanding this can only be used from within app.ts, so should I put this in app.ts, or is there a better, more modular way?

app.ts:

public next(nextPageString) {
   switch (nextPageString) {
            case "browse":
                this.nav.push(BrowsePage, {}, animationParams);
                break;
            case "goal-define":
                this.nav.push(GoalDefinePage, {}, animationParams);
                break;
...

Needless to say that above cases are endless. And I also did not find another way than to check for strings in order to get routing sorted out. Any ideas here are welcome.


#2

We are using ngrx now, which is honestly absolute bliss - highly recommended. Make sure to use in

package.json

"@ngrx/core": "1.0.0",
"@ngrx/store": "2.0.1",
"ionic-angular": "2.0.0-beta.11",
"@angular/common": "2.0.0-rc.4",

#3

Hi,

@nottinhill … I am also working on a small side project basically to get familiar with ionic 2 and ngrx/store and ngrx/effects.

All good so far and I do get the concepts especially of the ngrx libs.

But I do struggle on how and where to put the navigation (aka the navCtrl.push(…) statements).

Consider the very easy flow for a login use case

  • user enters on one page its credentials
  • an ngrx actions is dispatched and a corresponding reducer changes the state in the store
  • a side effect for this login_action performs the API call and once completed returns new actions (as LOGIN_FAILURE or LOGIN_SUCCESS) which changes the state again.

My question is … where to put the logic to navigate after login API call to navigate the user to either something like a dashboard or to an error display page etc. (in case of false credentials …)

I tried to inject the nav controller into the effects- although it smells bad - but without success.

Any ideas? How did you manage to integrate navigation into an ngrx/ionic 2 app?

Thanks,
S.


#4

Hi @me_ten,

it does smell a bit but arguably these are side effects. Does someone else have a better strategy? I’d be interested as well.

This is how you do it from within an @Effect:

import { App, AlertController } from 'ionic-angular';
import { Dashboard } from '../pages/dashboard/dashboard';

(...)

constructor(
  private app: App,
  private alertCtrl: AlertController,
  (...)
) {}

Import and inject the App and get the navigation controller from there.

@Effect() loginSuccess$ = this.actions$
  .ofType(AuthActions.LOGIN_SUCCESS)
  .map(action => action.payload)
  .do((payload) => {
     this.storage.set('access_token', payload.access_token);
     this.app.getActiveNav().setRoot(Dashboard);
  });

The alert works as usual.

@Effect() loginFailure$ = this.actions$
  .ofType(AuthActions.LOGIN_FAILURE)
  .map(action => action.payload)
  .do((payload) => {
     this.storage.remove('access_token');
     let alert = this.alertCtrl.create({
       title: 'Login failed',
       subTitle: payload.error_msg,
       buttons: ['OK']
     });
     alert.present();
});

#5

Hi @m165437,

I’m using a similar strategy but I have a Nav actions, reducer and relative effects:

// nav.actions.ts
//
export const ActionTypes = {
  PUSH:  type('[Nav] Push')
};

export class PushAction implements Action {
  type = ActionTypes.PUSH;

  constructor(public payload: NavInfo) { }
}

export type Actions
  = PushAction;
// nav.reducer.ts
//
export interface NavInfo {
  page: string;
  params?: any;
  isRootPage?: boolean;
  toRootNav?: boolean;
}

export interface State extends NavInfo {
}

export const initialState: State = {
  page: null,
  params: null,
  isRootPage: false,
  toRootNav: false
};

export function reducer(state = initialState, action: nav.Actions): State {
  console.debug(TAG, 'reducer()', { state, action });

  switch (action.type) {
    case nav.ActionTypes.PUSH: {
      const navInfo = (<nav.PushAction>action).payload;
      return Object.assign({}, state, navInfo);
    }

    default: {
      return state;
    }
  }
}
// nav.effects.ts
//
@Injectable()
export class NavEffects {
  @Effect({ dispatch: false })
  logout$: Observable<Action> = this.actions$
    .ofType(nav.ActionTypes.PUSH)
    .map(toPayload)
    .do((navInfo: NavInfo) => {
      console.debug(TAG, nav.ActionTypes.PUSH, { navInfo });

      const pageClass = resolvePage(navInfo.page);
      if (!pageClass) {
        console.warn(TAG, 'Page not found', navInfo.page);
        return;
      }

      const navCtrl = this.app.getActiveNav();
      if (navInfo.isRootPage) {
        navCtrl.setRoot(pageClass);
      } else {
        navCtrl.push(pageClass);
      }
    });

  constructor(
    private actions$: Actions,
    private app: App
  ) {
  }
}

In this way I have also the navigation state in my store.
But I have an issue using the time-traversal in ngrx/store-devtools (redux plugin) basically it’s not working because the side-effects are not triggered for some reason.

Could be nice if some ngrx/ionic experts will find some time to write a module for that, like router-store-module for angular-route :slight_smile:


#6

Hi @devmao,
i’m trying to use your solution to store my routing actions on ngrx, but I get lost on resolvePage(navInfo.page), do you resolve the page from a string? Are you using the page class name?
I couldn’t find anywhere in Ionic Api Docs a method to retrieve a page component from string


#7

I’ve just upgraded to Ionic v3 yesterday, but I’ve learned that if you use the @IonicPage() methodology you can refer to the page controls with their name, a string: perfect for storing in the redux store.
So I believe in the above code you do not need the resolvePage(), just make navCtrl.push(navInfo.page).


#8

Thank you ztp, I already found it on v3. Also I can’t get time-traversal to work…


#9

Anyone get time travel to work ?


#10

What do you mean when you say you decided to use relative effects? Do you mean you push and pop pages instead of setting the root nav?