Search code examples
angulartypescriptangular-routingngrxngrx-effects

Load entity after login, and navigate to entityID


Flow:

After the user successfully logs in, the app should load users Vendor and navigate to VendorID

Example: myUrl.com/vendor/ID

Different cases I'm trying to support:

1. A user puts it URL with Vendor ID:

App will reload Vendor

2. After login

App will load the Vendor and Navigate to URL

3. On refresh

App will re-load Vendor, and only navigate if URL does not exist already of VendorID (So the user will stay on the same page he is already at)

My issue:

When a user refreshes the app, he will be re-navigated to root Vendor (Not good, I want him to stay on his page, navigate only on login)

Code I have tried so far:

 @Effect()
  loadVendor(): Observable<VendorDataInitAction | AuthAction | VendorDataAction> {
    return this._actions$
      .pipe(
        ofType<AuthLoginSuccessAction | AuthRefreshSuccessAction>(AuthActions.LoginSuccess, AuthActions.RefreshSuccess),
        withLatestFrom(this._store.select(selectAuthActor)),
        filter(([_, actor]) => isUserLoggedIn(actor)),
        switchMap(([_, actor]) => {
          const errorActions = [
            new AuthSetAuthMessageAction({
              message: 'You need to be granted access in order to login to Portal.\n' +
                'Please contact your Platterz representative.',
              type: AuthMessageType.Error
            }),
            new AuthLogoutAction()
          ];

          return this._apollo
            .query<AssociatedRestaurants>({
              query: AssociatedRestaurantsQuery
            })
            .pipe(
              switchMap((result): ObservableInput<AuthAction | VendorDataAction> => {
                const errors = result.errors && result.errors.length > 0;
                const noRestaurants = !(result.data && result.data.associatedLocations &&
                  result.data.associatedLocations.locations.length);

                if (errors || noRestaurants) {
                  return errorActions;
                }

                return [
                  new VendorDataInitAction(result.data.associatedLocations.locations)
                ];
              }),
              catchError(() => errorActions)
            );
        }));
  }

  @Effect()
  navigateToVendorOnDataLoad(): Observable<VendorDidSetIDURLAction> {
    return this._actions$
      .pipe(
        ofType<VendorDidSetIDURLAction>(VendorDataActions.Init),
        withLatestFrom(this._route.params.pipe(map((params) => params.vendorID))),
        filter(([, vendorId]) => vendorId == null),
        switchMap(() => this._store.select(selectCurrentVendor)),
        filter((vendor) => !!vendor),
        take(1),
        map((vendor) => {

// When trying to get Route params, and navigate only if VendorID is null, Its always null...

          this._router.navigate(['/vendors', vendor.id, 'dashboard']);

          return new VendorDidSetIDURLAction();
        })
      );
  }

I tried accessing Route params on @Effect with no success, it does not contain VendorID while refreshing...

How is it possible to get Route params from @Effect? Or is there a better way of archiving this logic?


Solution

  • Posting my solution for anyone else bumping to a similar issue.

    Seems as URL params are not accessible at times from @Effects (Specifically on refresh) So I used local storage instead.

    Flow goes as the following:

    1. By default, the app will always navigate you to sign-in page in case you do not have any URL params (Enter base app URL)

    2. Any auth component has a CanActive guard, that checks for logged in state, if logged in, navigates to current Vendor, if not, sign-in will be displayed.

    First route:

    { path: '', redirectTo: 'auth', pathMatch: 'full' },
      {
        path: 'auth',
        component: AuthPageComponent,
        canActivate: [AppLoggedOutGuard],
        children: [
          { path: '', redirectTo: 'sign-in', pathMatch: 'full' },
          {
            path: 'sign-in',
            component: LoginPageComponent
          },
          {
            path: 'sign-up',
            data: { signUpBenefits },
            component: VendorSignUpComponent
          }
        ]
      },
    

    LoggedOut guard

    @Injectable()
    export class AppLoggedOutGuard extends AppRoleGuard implements CanActivate {
      constructor(
        store: Store<IAppState>,
        router: Router,
        private _appAuthService: AppAuthService
        ) {
        super(router, store);
      }
    
      canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
        return this._user$
          .pipe(
            take(1),
            switchMap(() => this._appAuthService.getLoggedInState()),
            map((isLoggedIn) => {
              if (isLoggedIn) {
                this._navigateToVendor();
    
                return false;
              }
    
              return true;
            })
          );
      }
    
      private _navigateToVendor(): void {
        this._store.select(selectCurrentVendor)
          .pipe(
            filter((vendor) => !!vendor),
            take(1)
          ).subscribe((vendor) => {
            this._router.navigate(['/vendors', vendor.id, 'dashboard']);
          });
      }
    }
    

    For fetching extra data, you can use a resolver on the Vendor(Or you entity) base component