universesurfer
universesurfer

Reputation: 357

Why is my user ID undefined when passing it into my URL?

I am building a profile page and trying to get the authenticated user data to display there. My API call works with their id, and it works on the front end if I manually enter the id into the url.

But when I try to navigate to the profile from the navbar, I receive a

400 Bad Request for URL: http://localhost:3000/users/undefined

What I can assume right now is that it's an asynchrony issue. My profile page calls the user data, but that user data isn't available in my nav component. And it seems as though I need to pass in my id param into my profile [routerLink] if I want to navigate correctly. Since my user data isn't available in my nav component, it has nothing to pass.

Is there a better approach to this? Should I be using an event emitter?

Fairly new to Angular - help much appreciated!

Profile Component

import { Component, OnInit, Input } from '@angular/core';
import { AuthService } from '.././services/auth.service';
import { UserService } from '.././services/user.service'
import { Router, ActivatedRoute } from '@angular/router';


@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.css'],
  providers: [UserService]
})
export class ProfileComponent implements OnInit {

  currentUser;

  isAuth: boolean;

  constructor(
    private session: AuthService,
    private router:  Router,
    private userService: UserService,
    private route: ActivatedRoute

  ) {


  this.session.isAuth
       .subscribe((isAuth: boolean) => {
       // user will be false if logged out
       // or user object if logged in.
         this.isAuth = isAuth;
       });
   if (this.session.token) {
     this.isAuth = true;
     console.log(this.session);
   } else {
     this.isAuth = false;
   }

  }




  ngOnInit() {

    this.route.params.subscribe(params => {
      this.getUserDetails(params['id']);
    });


  }

  getUserDetails(id) {
     this.userService.get(id)
       .subscribe((user) => {
         this.currentUser = user;
         console.log(this.currentUser);
       });
   }


  }

Nav Template

Where I'm navigating to my profile page.

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">bnb</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav navbar-right">


        <li *ngIf="!isAuth"><a [routerLink]="['login']">Login</a></li>
        <li *ngIf="isAuth"><a [routerLink]="['profile']"><span class="glyphicon glyphicon-user" aria-hidden="true"></span>&nbsp;&nbsp; Profile</a></li>
        <li *ngIf="isAuth"><a (click)="logout()">Logout</a></li>
        <li *ngIf="!isAuth"><a [routerLink]="['signup']">Signup</a></li>

      </ul>
    </div>
  </div>
</nav>

Nav Component

import { Component, OnInit, Input } from '@angular/core';
import { AuthService } from '.././services/auth.service';
import { UserService } from '.././services/user.service';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {

  isAuth: boolean;

  currentUser: any;


  constructor(
    private session: AuthService,
    private userService: UserService,
    private router: Router,
    private route: ActivatedRoute
  ) {

    this.currentUser = JSON.parse(localStorage.getItem("User"))
    console.log("USER",this.currentUser)                       //Currently returns Null
    console.log(this.session)
    this.session.isAuth
      .subscribe((isAuth: boolean) => {
      // user will be false if logged out
      // or user object if logged in.
        this.isAuth = isAuth;
      });
  if (this.session.token) {
    this.isAuth = true;
  } else {
    this.isAuth = false;
  }
}

  ngOnInit() {

  }


  logout() {
   this.session.logout();

 }




}

Router

import { Routes } from '@angular/router';

import { LoginComponent } from '../login/login.component';
import { SignupComponent } from '../signup/signup.component';
import { HomeComponent } from '../home/home.component';
import { RentalListingsComponent } from '../rental-listings/rental-listings.component';
import { SingleRentalComponent } from '../rental-listings/single-rental/single-rental.component';
import { ProfileComponent } from '../profile/profile.component'
import { AuthService } from '../services/auth.service';


export const routes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'login', component: LoginComponent },
    { path: 'signup', component: SignupComponent },
    { path: 'rentals', component: RentalListingsComponent },
    { path: 'listing', component: SingleRentalComponent },
    { path: 'profile/:id', component: ProfileComponent, canActivate: [AuthService] }   <--profile path.  I know I have to match my url paths, but don't know how to do this from the navbar.  
    // { path: 'home', component: HomeComponent, canActivate: [AuthService] },

    { path: '**', redirectTo: '' }
];

Upvotes: 0

Views: 2598

Answers (2)

Dan
Dan

Reputation: 1355

Thanks for providing the detail. Somewhere you need to subscribe to 'after login' or 'authentication' event, grab the user profile JSON, and save it to localstorage so you can use it anywhere you want. If you can't hook in or subscribe to one of these, then do it imperatively somewhere convenient in your code. Find out what call you can make to fetch the entire user JSON and save it as follows...

Check out my AuthService init() below. First line is this.authProvider.on('authenticated', this.onAuth);. Whatever authentication service API you are using should provide a way for you to specify a callback (providing the login token) whenever someone logs in. The onAuth callback function saves the token in localstorage and then fetchProfile(...){...} makes another call to the authentication service API to get the whole JSON user profile using the token just received this.user.getProfile(idToken, this.onProfile);. For example, I use Auth0 in projects, and my call to Auth0 API looks like this.lock.getProfile(idToken, this.onProfile); but I replaced that with an example of what your call might look like this.user.getProfile(idToken, this.onProfile); So use whatever your API uses replace in fetchProfile. Then the onProfile callback saves the entire JSON profile in a single key in local storage using this.localStorage.set('profile', profile); Then you can get it any time by calling this.localStorage.get('profile').

  1. Do not provide UserService through the lazy-loaded ProfileComponent. That creates a separate branch on the dependency injection tree you might not want. See https://angular-2-training-book.rangle.io/handout/modules/shared-modules-di.html Import the UserService in a top-level module like AppModule or SharedModule and provide it there. No need to export it if it's in AppModule.

app.module.ts

...
@NgModule({
  imports: [
    ...
    UserService,
    ...
    ]
  providers: [
    ...
    UserService,
    ...
    ]
  1. Handle Auth related stuff in Auth, not Profile. Profile seems visual/implementation-specific (e.g. it has a template). Here is a code snippet example.

auth.service.ts

@Injectable()
export class Auth {

  userProfile: UserProfile;

  constructor(
              ...
              private localStorage: LocalStorageService,
              private router: Router,
              private user: UserService,
              private authProvider: ...
              ...
              ) {
  }

  init() { 
      this.authProvider.on('authenticated', this.onAuth);
      // Set userProfile attribute if already saved profile
      this.userProfile = this.localStorage.get('profile');
      setTimeout(() => { // let AppComponent listener initialize
        this.localStorage.set('profile', this.userProfile);
      }, 0);
    }
  }

  onAuth = (authResult: AuthResult) => {
    this.localStorage.set('id_token', authResult.idToken);
    this.fetchProfile(authResult.idToken);
  }

  // Save current route for redirect url
  login() {
    this.localStorage.set('redirect_url', this.router.url);
    this.authProvider.show({initialScreen: 'login'});
  };

  // Check if user is logged in.
  authenticated() {
    // Check if unexpired token. 
    // Searches for item in localStorage with key == 'id_token'
    return this.authProvider.tokenNotExpired();
  };

  logout() {
    this.router.navigateByUrl('');
    this.userProfile = undefined; // do before localstorage
    this.localStorage.remove('id_token');
    this.localStorage.remove('profile');
  };

  fetchProfile(idToken: string) {
    this.user.getProfile(idToken, this.onProfile);
  }

  /**
   * On profile event callback.
   * Save profile to LocalStorage. 
   * Redirect to url if present in LocalStorage.
   */
  onProfile = (error: any, profile: UserProfile) => {
    if (error) {
      console.log(error);
      return;
    }
    this.userProfile = profile; 
    this.localStorage.set('profile', profile);

    // Redirect if there is a saved url to do so.
    const redirectUrl: string = this.localStorage.get('redirect_url');
    if (redirectUrl !== undefined) {
      this.router.navigateByUrl(redirectUrl);
      this.localStorage.remove('redirect_url');
    }
  }
  1. Interact with localStorage through a LocalStorageService and subscribe to changes as follows.

localstorage.service.ts

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class LocalStorageService {
  [key:string]: any;

  /**
   * define your localstorage variables here as observables
   */
  id_token$ = new Subject();
  redirect_url$ = new Subject();
  profile$ = new Subject();
  customer$ = new Subject();

  set(key: string, value: any) {
    this[key + '$'].next(value); // this will make sure to tell every subscriber about the change.
    localStorage.setItem(key, JSON.stringify(value));
  }

  get(key: string) {
    const value = localStorage.getItem(key);
    return value && JSON.parse(value);
  }

  remove(key: string) {
    this[key + '$'].next(undefined);
    localStorage.removeItem(key);
  }
}
  1. Don't do so much in constructor. Example:

app.component.ts

 export class AppComponent implements OnDestroy, OnInit {


    webRobot: boolean = false;

    private profileSub: any;
    private customerSub: any;
    private subscriptionSub: any;

    constructor(
          private analyticsService: AnalyticsService,
          private auth: Auth,
          private localStorage: LocalStorageService,
          ) {
          }

    ngOnInit(): void {
         this.init();
    }

    init() {
        this.auth.init(this.webRobot);
        this.analytics.init(this.webRobot);
        if (!this.webRobot) {
          // subscribe to authed profile changes
          this.profileSub = 
                   this.localStorage.profile$.subscribe(this.onProfile);
          // Subscribe to changes to Stripe customer
          this.customerSub = 
                   this.localStorage.customer$.subscribe(this.onCustomer);
      }


  // always delete active subscribes on destroy
  ngOnDestroy() {
    this.profileSub.unsubscribe();
    this.customerSub.unsubscribe();
  }

  onProfile = (profile: UserProfile) => { 

  // ...do stuff        

  }

  onCustomer= (profile: Customer) => {

  // ...do stuff

  }

Upvotes: 1

Eddie Martinez
Eddie Martinez

Reputation: 13910

In your profile route configuration, it is expecting the id query param

 { path: 'profile/:id', component: ProfileComponent, canActivate: [AuthService] }   
<--profile path.  
 I know I have to match my url paths, 
 but don't know how to do this from the navbar. 

but your navbar link is not passing the id value

 <li *ngIf="isAuth"><a [routerLink]="['profile']"><span class="glyphic

you need to do something like this in your navbar

 <li *ngIf="isAuth"><a [routerLink]="['profile/user.id']">

Upvotes: 0

Related Questions