0

I have this problem when I log in to the username section, I get undefined undefined instead of the username that comes from my database, but the moment I refresh the full page, I get the username correctly. I don't know why. I attach my code since I can't see where I'm wrong:

enter image description here

auth.services.ts

import { IMeData, ISession } from '@core/services/interfaces/session.interface';
import { LOGIN_QUERY, ME_DATA_QUERY } from '@graphql/operations/query/user';
import { Injectable } from '@angular/core';
import { ApiService } from '@graphql/services/api.service';
import { Apollo } from 'apollo-angular';
import { map } from 'rxjs/operators';
import { HttpHeaders } from '@angular/common/http';
import { Subject } from 'rxjs';
@Injectable({
  providedIn: 'root',
})
export class AuthService extends ApiService {
  accessVar = new Subject<IMeData>();
  accessVar$ = this.accessVar.asObservable();

  constructor(apollo: Apollo) {
    super(apollo);
  }

  updateSession(newValue: IMeData) {
    this.accessVar.next(newValue);
  }

  start() {
    if (this.getSession() !== null) {
      this.getMe().subscribe((result: IMeData) => {
        if (!result.status) {
          this.resetSession();
          return;
        }
        this.updateSession(result);
      });
      console.log('Sesión iniciada');
      return;
    }
    this.updateSession({
      status: false,
    });
    console.log('Sesión no iniciada');
  }

  /** Añadir los metodos para consumir la info de la API */
  login(email: string, password: string) {
    return this.get(LOGIN_QUERY, { email, password, include: false }).pipe(
      map((result: any) => {
        return result.login;
      })
    );
  }

  getMe() {
    return this.get(ME_DATA_QUERY, {
        include: false,
      },
      {
        headers: new HttpHeaders({
          Authorization: (this.getSession() as ISession).token
        })
      }).pipe(map((result: any) => {
        return result.me;
      })
    );
  }

  setSession(token: string, expiresTimeInHours = 24) {
    const date = new Date();
    date.setHours(date.getHours() + expiresTimeInHours);

    const session: ISession = {
      expiresIn: new Date(date).toISOString(),
      token
    };
    localStorage.setItem('session', JSON.stringify(session));
  }

  getSession(): ISession {
    return JSON.parse(localStorage.getItem('session'));
  }

  resetSession() {
    localStorage.removeItem('session');
    this.updateSession({status: false});
  }
}

login.component.ts

import { Component } from '@angular/core';
import { AuthService } from '@core/services/auth.service';
import { ILoginForm, IResultLogin, } from '@core/services/interfaces/login.interface';
import { basicAlert } from '@shared/alerts/toasts';
import { TYPE_ALERT } from '@shared/alerts/values.config';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent {
  login: ILoginForm = {
    email: '',
    password: '',
  };
  constructor(private auth: AuthService, private router: Router) {}

  init() {
    console.log(this.login);
    this.auth.login(this.login.email, this.login.password).subscribe(
      (result: IResultLogin) => {
        console.log(result);
        if (result.status) {
          if (result.token !== null) {
            basicAlert(TYPE_ALERT.SUCCESS, result.message);
            this.auth.setSession(result.token);
            this.auth.updateSession(result);
            this.router.navigate(['/home']);
            return;
          }
          basicAlert(TYPE_ALERT.WARNING, result.message);
          return;
        }
        basicAlert(TYPE_ALERT.INFO, result.message);
      });
  }
}

navbar.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '@core/services/auth.service';
import { IMeData } from '@core/services/interfaces/session.interface';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.scss'],
})
export class NavbarComponent implements OnInit {
  session: IMeData = {
    status: false,
  };
  access = false;
  role: string;
  userLabel = '';

  constructor(private authService: AuthService) {
    this.authService.accessVar$.subscribe((result) => {
      console.log(result.status);
      this.session = result;
      this.access = this.session.status;
      this.role = this.session.user?.role;
      this.userLabel = `${ this.session.user?.name } ${ this.session.user?.lastname }`;
    });
  }

  ngOnInit(): void {}

  logout() {
    this.authService.resetSession();
  }
}

public.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '@core/services/auth.service';

@Component({
  selector: 'app-public',
  templateUrl: './public.component.html',
  styleUrls: ['./public.component.scss'],
})
export class PublicComponent implements OnInit {
  constructor(private auth: AuthService) {}

  ngOnInit(): void {
    this.auth.start();
  }
}

  • The two block codes are identical, I think you missed your `auth.services.ts`. – Alisson Reinaldo Silva Dec 28 '21 at 03:19
  • You are absolutely right, my code was not copied from the auth.service, I just edited my question and I already updated my code, could you help me to review it again please? Thank you very much. – Asher Wyatt Dec 28 '21 at 05:23
  • What is the code you use to get the username and display in the menu on top? And where do you use `accessVar$` from auth service? What about the `start()` method from auth service? I don't see these things being used in your sample code, there are still some missing pieces. BUT i must say this issue seems related to wrong observables usage. – Alisson Reinaldo Silva Dec 28 '21 at 06:20
  • Hello good morning, I just put my code (navbar.component.ts) where I am sending the user in the upper part of my navbar, I also use accessVar $, and the start () I am using for the public part public.component .ts – Asher Wyatt Dec 28 '21 at 16:40
  • Please check my answer, let me know if it helps (don't forget to mark it as accepted if that's the case). – Alisson Reinaldo Silva Dec 29 '21 at 00:22

2 Answers2

0

First be sure to validate the result before accessing it, or you can use "?"

if (result?.status) {
          if (result?.token !== null) {

Why does it call the service inside the init() in your login component
Create a onSubmit function in your login component:


public onSubmit(): void {
    const { email, password } = this.login;
    if (email && password) {
      this.auth.login(email, password);
    }
  }

In your login html:

...
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
   ...
</form>

loginForm is email and password

Please see Forms in Angular

And your AuthService I can't see, your paste the same code.

Now with the documentation of angular your login component it should look like this:

import { Component } from '@angular/core';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { AuthService } from '@core/services/auth.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent {
  public loginForm: FormGroup;

  constructor(private fb: FormBuilder, private auth: AuthService) {
    this.loginForm = this.fb.group({
      email: ['', Validators.required],
      password: ['', Validators.required],
    });
  }

  public onSubmit(): void {
    const { email, password } = this.loginForm.value;
    if (email && password) {
      this.auth.login(email, password);
    }
  }
}

Edited

I think the problem is in getMe function in your auth.service.ts

Debug result variable in this function. And check if result.me has status, user etc.

Or maybe it should be return result

Camilo Gomez
  • 165
  • 1
  • 6
  • Thank you very much, I just updated my repeating code since I had duplicated the same code, could you help me to see where I am wrong please? Thank you very much and yes, my code is similar to the one you put here. – Asher Wyatt Dec 28 '21 at 05:25
  • I think the problem is in getMe function in your auth.service.ts. Debug result variable in this function. And check if result.me has status, user etc. Or maybe it should be return result. If result has { me: {user:{name, ...}}} then your interface IMeData must have the same structure – Camilo Gomez Dec 28 '21 at 21:19
  • 153 / 5.000 Resultados de traducción The truth is that I have been looking for a while to see where the error is and I have already tried all your recommendations and no days. I'll keep trying, thank you very much! – Asher Wyatt Dec 29 '21 at 00:09
0

In your auth.service.ts, instead of:

accessVar = new Subject<IMeData>();

use this:

accessVar = new BehaviorSubject<IMeData>({ status: false });

The Subject doesn't keep the last emitted value, so when you subscribe to it, you only get new values. On the other hand, the BehaviorSubject keeps the last emitted value, so when you subscribe to it, you get the last emitted value.

Check this -> What is the difference between Subject and BehaviorSubject?

Why it doesn't work when you first login?

This is the order of events happening when you first login:

  • ngOnInit (public component)
  • auth.start() (public component)
  • "Sesión no iniciada" (as your session is still empty)
  • User proceeds to login
  • auth.setSession (login component)
  • auth.updateSession (login component)
  • this.accessVar.next(newValue) (auth service)
  • navigate(['/home'])
  • authService.accessVar$.subscribe (NavBar component)

See how the authService.accessVar$.subscribe in your navbar component is called after the accessVar.next() call in your auth service, so the code inside your subscribe (in navbar) never runs.

Why it works when you refresh?

This is the order of events when you refresh the page:

  • authService.accessVar$.subscribe (NavBar component)
  • ngOnInit (public component)
  • auth.start() (public component)
  • Session is available in localStorage
  • this.getMe() (auth service)
  • this.updateSession(result) (auth service > start method)
  • this.accessVar.next(newValue) (auth service)

Here at the time we call accessVar.next(), the navbar component was already subscribed to it, waiting for new values, so the subscribe()callback gets executed.

If you change it to BehaviorSubject, it should work even in the first case, because even if the navbar subscribes to it at a later point, the BehaviorSubject will emit the last emitted value.

Alisson Reinaldo Silva
  • 10,009
  • 5
  • 65
  • 83
  • I just tried it and it should work as you say along with the differences between subject, but it remains the same, it continues to show me undefined undefine, and this is what it sends me in my inpector of elements from the browser. , I'll keep trying. – Asher Wyatt Dec 29 '21 at 03:01