2

Background

In my angular2 application I am using Auth0 to authenticate. After the redirect back to my website I display some of the user's data to them in the component. When the page renders it does not show the user's email or gravatar / image. But at that point if I refresh the page the gravatar and email shows.

Setup

In app.component.html I am adding the breadcrumb component directly under the navigation but directly on top of the router-outlet.

The breadcrumb is shown on every page. If someone is not logged in it shows a Login / Register link. If someone is logged in it is supposed to show a picture of the user and their email which is returned in the auth0 profile saved to the localStorage.

Inside the component that is rendered to the router-outlet all of the data that is in localStorage is show in the page. This page renders perfectly. But the breadcrumb component does not update with the correct data from localStorage.

It is obvious that the problem here is that the breadcrumb component is being rendered while the profile in localStorage is still null.

Code Example

I am displaying the data like this,

breadcrumb.component.html

<ul class="nav navbar-nav pull-right" *ngIf="!auth.authenticated()">
  <li class="nav-item">
    <a class="nav-link" (click)="auth.login()">Login / SignUp</a>
  </li>
</ul>

<ul class="nav navbar-nav pull-right" *ngIf="auth.authenticated()">
  <li class="nav-item">
    <a class="aside-toggle" href="#" role="button" aria-haspopup="true" aria-expanded="false">

      <span *ngIf="auth.userProfile.nickname" class="profile-name">{{auth.userProfile.nickname}}</span>
      <span *ngIf="!auth.userProfile.nickname" class="profile-name">Account</span>

      <i class="icon-bell"></i><span class="tag tag-pill tag-danger profile-alerts">5</span>

      <img *ngIf="auth.userProfile.picture" [src]="auth.userProfile.picture" class="img-avatar profile-picture" alt="User profile picture">
      <img *ngIf="!auth.userProfile.picture" src="assets/img/avatars/gravatar-default.png" class="img-avatar profile-picture" alt="Default profile-picture">

    </a>
  </li>
</ul>

Please notice I have tried verifying and assigning the data in multiple different ways using built in NG2 features.

breadcrumb.component.ts

import { Component, OnInit, AfterViewInit, OnChanges } from '@angular/core';
import { Router, ActivatedRoute }    from '@angular/router';
import { Auth }                      from './../services/auth.service';

@Component({
    selector: 'breadcrumbs',
    templateUrl: './breadcrumb.component.html'
})
export class BreadcrumbsComponent implements OnInit, AfterViewInit, OnChanges {
  private gravatar: string
  private email: string;

  constructor(private router:Router, private route:ActivatedRoute, private auth: Auth) {
      if (this.auth.authenticated()) {
        var userProfile = JSON.parse(localStorage.getItem('profile'));
        this.gravatar = userProfile.picture;
        this.email = userProfile.email;
      }
    }

  ngOnChanges() {
    if (this.auth.authenticated()) {
      var userProfile = JSON.parse(localStorage.getItem('profile'));
      this.gravatar = userProfile.picture;
      this.email = userProfile.email;
    }
  }

  ngOnInit() {
    if (this.auth.authenticated()) {
      var userProfile = JSON.parse(localStorage.getItem('profile'));
      this.gravatar = userProfile.picture;
      this.email = userProfile.email;
    }
  }

  ngAfterViewInit() {
    if (this.auth.authenticated()) {
      var userProfile = JSON.parse(localStorage.getItem('profile'));
      this.gravatar = userProfile.picture;
      this.email = userProfile.email;
    }
  }
}

When I check the local storage the data does exist. So this leads me to believe that the page is rendering before the data is able to be placed in the local storage.

This is the majority of my auth0 service.

authorize.service.ts

@Injectable()
export class Auth {
  lock = new Auth0Lock(myConfig.clientID, myConfig.domain, options, {});
  userProfile: Object;
  logreg: LogReg;
  user: User;

  constructor(private router: Router, private http: Http ) {

    this.userProfile = JSON.parse(localStorage.getItem('profile'));
    this.user = JSON.parse(localStorage.getItem('user'));
    this.lock.on('authenticated', (authResult: any) => {
      localStorage.setItem('access_token', authResult.idToken);
      this.lock.getProfile(authResult.idToken, (error: any, profile: any) => {
        if (error) {
          console.log(error);
          return;
        }

          // Login Or Register User On Our Server
        this.logreg = new LogReg(profile.email_verified, profile.email);

        this.checkRegister(this.logreg).subscribe(
            (res)=>{
              console.log("Hey this runs");
              console.log(res);
                if (res.email_verified === false) {
                 localStorage.removeItem('profile');
                 localStorage.removeItem('api_key');
                 localStorage.removeItem('access_token');
                 localStorage.removeItem('user');
                 this.userProfile = null;
                 this.user = null;
                 this.router.navigate(['/verify-email']);

                }
            else if (res.api_key_exist === false) {
                  console.log("Hey this works")
                localStorage.setItem('profile', JSON.stringify(profile));
                this.userProfile = profile;
                console.log(this.userProfile);
                this.user = new User(profile.email, '', '', '', '', '', '', '', '', '', '', res.api_key_exist, '')
                localStorage.setItem('user', JSON.stringify(this.user));
                this.router.navigate(['/profile']);

            } else if (res.api_key_exist === true) {
                this.user = new User(res.user.email,
                    res.user.first_name,
                    res.user.middle_name,
                    res.user.last_name,
                    res.user.dob,
                    res.user.phone,
                    res.user.street_address,
                    res.user.city_address,
                    res.user.state_address,
                    res.user.zip_address,
                    res.user.client_ss,
                    res.api_key_exist,
                    res.api_key);
                console.log(this.user);
                localStorage.setItem('api_key', JSON.stringify(res.api_key));
                localStorage.setItem('user', JSON.stringify(this.user));
                localStorage.setItem('profile', JSON.stringify(profile));
                this.router.navigate(['/overview']);
            }
        },
            (err)=>{ console.log(err);}

        );
      });
      this.lock.hide();
    });
  }

  public checkRegister(model: LogReg) {
      // Parameters obj-
      let params: URLSearchParams = new URLSearchParams();
      params.set('email', model.email);
      params.set('email_verified', model.email_verified);

      return this.http.get(STATICS.API_BASE + STATICS.API_LOGIN,
          { search: params }).map((res:Response) =>  res.json());
      }

  public login() {
    this.lock.show();
  }
}

Question

How would I go about making sure the data exist before completing the rendering of the page when someone logs in? This way I can make sure the correct data is displayed to the user.

wuno
  • 9,547
  • 19
  • 96
  • 180

1 Answers1

0

I cannot comment I will write this as an answer. I have the same issue and I resolved it doing this:

edit

try this:

first I use this custom event emitter that makes life easy:

import { Injectable, EventEmitter } from '@angular/core';
@Injectable()
export class EmitterService {
   private static _emitters: { [ID: string]: EventEmitter<any> } = {};

   static get(ID: string): EventEmitter<any> {
      if (!this._emitters[ID]) 
        this._emitters[ID] = new EventEmitter();
      return this._emitters[ID];
    }

 }

inside the auth.service:

constructor(private auth: Auth, private issuesService: IssuesService) {
if (this.auth.authenticated()){
    this.userProfile = this.auth.getProfile()
    this.username = this.userProfile.name
}
else {
  this.loadUserDetails().subscribe(_ => {
    this.username= this.userProfile.name 
  })
  }
 }

 ngOnInit() {
  if (this.auth.authenticated()){
    this.userProfile = this.auth.getProfile()
    this.username = this.userProfile.name
    console.log(this.username)
 }
 else {
  this.loadUserDetails().subscribe(_ => {
    this.username= this.userProfile.name 
  })
}
}

loadUserDetails(){
 return EmitterService.get('USER_LOGGED_IN').map(userProfile => { 
    this.userProfile= userProfile
  });
}

and in my html, I have:

<li><a [hidden]="!username" *ngIf="auth.authenticated()">Welcome, {{username}}</a></li>

hope this helps!

elmiomar
  • 1,747
  • 2
  • 17
  • 27
  • Thanks buddy I will check itout – wuno Mar 01 '17 at 21:16
  • Hey man I updated my question to show you what I did. None of that helps. I don't understand why. The breadcrumb component is part of the app template. Which is called in app.component. html. I did not have this problem till I added lazy loading. DO you think I need to add this to every single lazy loaded component instead of the breadcrumb component which is where the html is for showing this information? – wuno Mar 01 '17 at 21:38
  • Ya so I can output the date in the overview component form the local storage. But the breadcrumbs component evidently is not re rendering with the updated data. I will read through your edit. – wuno Mar 01 '17 at 22:02
  • Yes, but that's what basically your authentication service does. it gets data from the local storage if the user is already authenticated. unless the token has expired. – elmiomar Mar 01 '17 at 22:07
  • But my service sets the data in the local storage when it is authenticated. And updates the components with the data. But the breadcrumb is not updating till after the pages is refreshed. Is there a way to force the breadcrumb component to rerender after the user logs in? – wuno Mar 01 '17 at 22:47
  • so your breadcrumb needs to implement the [ngOnChanges](https://angular.io/docs/ts/latest/api/core/index/OnChanges-class.html) and bind it to the `userProfile`. – elmiomar Mar 01 '17 at 22:55
  • By the way, I am using the same code with the event emitter above, Instead of breadcrumb I have a navbar component and It's working fine. No need to refresh the page. – elmiomar Mar 01 '17 at 23:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137002/discussion-between-omarilias-and-wuno). – elmiomar Mar 01 '17 at 23:27
  • I tried to implement your code. it did not work for me. I was up till 5am trying to make it work. I would like the shared/breadcrumb.component to update when new data is provided to the localStorage by the auth.service. Did you see the git link I sent you in the chat? – wuno Mar 02 '17 at 18:55
  • Yes, I did! go to the conversation. I told it worked for me. – elmiomar Mar 02 '17 at 18:56
  • Ok. Well your code worked in your app. Would you mind looking at my code on git and see how I can make this work correctly? the code is in the authorize.service.ts file and the breadcrumb.component.ts and breadcrumb.component.html – wuno Mar 02 '17 at 18:58
  • @wuno sent you an email – elmiomar Mar 03 '17 at 17:00
  • WOW. Thanks man. I cant tell you how much I appreciate you. It works perfectly. I have one concern though. Please look at this http://stackoverflow.com/a/34402906/2218253 Do you think we should be doing this with a behavior subject or observable based on that previous answer. This one explains that, http://stackoverflow.com/a/40231605/2218253 What are your thoughts on this? – wuno Mar 03 '17 at 19:49
  • You're welcome! To be honest, I am not really familiar with BehaviorSubject. But by looking into the [code](https://github.com/Reactive-Extensions/RxJS/blob/master/src/core/subjects/behaviorsubject.js#L102) BehaviorSubject are just a special type of Observable. And [EventEmitter](https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html) extends Subject, which is a type of Observable too. So to answer your question it doesn't really matter to me, as long as it gets the job done. If I find anything about it in the future I'll let you know. – elmiomar Mar 03 '17 at 20:49
  • Awesome man. Thanks again and have an amazing week. You deserve it. – wuno Mar 03 '17 at 20:51