BraveOtter
BraveOtter

Reputation: 114

How to reload information from subscrition in Angular?

In my navbar has a dropdown for login and for the user options. Using ngIf I show and hide each menu as appropriate. For example, when the user isn't logged I show this:

But, when some user is logged I show this one:

I did this using asking to my backend if the token is valid and the user is logged. And I do it using a subscription. The problem is that when I click "Sign in" or "Sign out" the component does not reload. I need to reload the page for it to work. What is the best method to get a new value from a subscription without reload all the page? Because I don't think reload all the page were a good experience for user.

This is my code:

navbar.component.html

<ul class="navbar-nav mr-right py-0">
    <li *ngIf="!isLogged; else elseBlock" class="nav-item dropdown">
        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            Sign in or register
        </a>
        <div class="dropdown-menu p-3 dropdown-menu-right">
            <app-login></app-login>
        </div>
    </li>
    <ng-template #elseBlock>
        <li class="nav-item dropdown">
            <a class="nav-link py-0" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                <img src="{{avatarUrl}}" class="rounded-circle d-inline-block p-0 profile-img mx-3" alt="{{username}} avatar">
                <span class="dropdown-toggle p-0 m-0 align-middle">{{username}}</span>
            </a>
            <ul class="dropdown-menu-right dropdown-menu" aria-labelledby="dropdownMenuLink">
                <li><span class="dropdown-item clickable" (click)="goToProfile()">Profile</span></li>
                <li><span class="dropdown-item clickable" (click)="logout()">Sign out</span></li>
            </ul>
        </li>
    </ng-template>
</ul>

navbar.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { AccountService } from '../account/account.service';
import { AuthService } from '../auth/auth.service';

@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
  
  isLogged: boolean;
  avatarUrl: string;
  username: string;
  isLogged_subscription: Subscription;
  avatar_subscription: Subscription;
  username_subscription: Subscription;

  constructor(private authService: AuthService, private accountService: AccountService, private router: Router) { }

  ngOnInit(): void {
    this.isLogged_subscription = this.authService.isLogged().subscribe(
      value => {
        this.isLogged = value;
      }
    );

    this.avatar_subscription = this.accountService.getAvatar().subscribe(
      value => {
        this.avatarUrl = value;
      }
    );
    this.username_subscription = this.accountService.getUsername().subscribe(
      value => {
        this.username = value;
      }
    );
  }

  logout(): void {
    this.authService.logout();
  }

  goToProfile(): void {
    this.router.navigateByUrl('/profile')
  }
}

auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { Observable, BehaviorSubject } from 'rxjs';
import { UserI } from '../models/user';
import { ServerResponseI } from '../models/server-response';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})

export class AuthService {
  constructor(private httpClient: HttpClient) { }

  register(user: UserI): Observable<ServerResponseI> {
    return this.httpClient.post<ServerResponseI>(`${environment.apiBase}/account/register`, user).pipe(
      tap(
        (res: ServerResponseI) => {
          if(res){
            this.saveToken(res.token, res.expiry)
          }
        }
      )
    );
  }

  login(user: UserI): Observable<ServerResponseI> {
    return this.httpClient.post<ServerResponseI>(`${environment.apiBase}/account/get_token/`, user).pipe(
      tap(
        (res: ServerResponseI) => {
          if(res){
            this.saveToken(res.token, res.expiry)
          }
        }
      )
    );
  }

  isLogged(): Observable<boolean> {
    return this.httpClient.get<boolean>(`${environment.apiBase}/account/is_logged/`);
  }

  logout(): void{
    localStorage.removeItem("ACCESS_TOKEN");
    localStorage.removeItem("EXPIERES_IN");
  }

  private saveToken(token: string, expiresIn: string): void {
    localStorage.setItem("ACCESS_TOKEN", token);
    localStorage.setItem("EXPIERES_IN", expiresIn);
  }
}

Upvotes: 0

Views: 189

Answers (1)

Vladlen Tereshchenko
Vladlen Tereshchenko

Reputation: 91

The reason why you need to reload the page to see changes is because you only update "isLogged" variable in ngOnInit.

I'll start with logout since I don't see the code for the login component.

The simplest solution would look like this:

logout(): void {
    this.authService.logout();
    this.isLogged = false;
}

If you want to make sure that user is logged out by asking you back-end, then you can do it like this:

logout(): void {
    this.authService.logout();
    this.updateLoginStatus();
}

// extracting this logic into a separate function gives us an ability 
// to use it here and in ngOnInit
updateLoginStatus(): void {
    // no need to save subscription if we don't plan to unsubscribe later
    this.authService.isLogged().subscribe(value => this.isLogged = value)
}

To update your component after login you could create an event emitter in your login component and bind to it in navbar, which would look like this:

export class LoginComponent {
    @Output() loggedIn = new EventEmitter()

    // function that handles click on "Log In" button
    buttonHandler() {
        // ... some of your login
      
       this.loggedIn.emit()
    }
}

After that you need to listen for this event in your navbar.component.html like this:

<app-login (loggedIn)="updateLoginStatus()"></app-login>

Or simpler version:

<app-login (loggedIn)="isLogged = true"></app-login>

Upvotes: 1

Related Questions