Как перезагрузить информацию из подписки в Angular?

#angular #authentication #service #frontend

#angular #аутентификация #Обслуживание #интерфейс

Вопрос:

В моей навигационной панели есть выпадающий список для входа в систему и для пользовательских параметров. Используя ngIf, я показываю и скрываю каждое меню соответствующим образом. Например, когда пользователь не зарегистрирован, я показываю это:

Но, когда какой-либо пользователь регистрируется, я показываю это:

Я сделал это, запросив у своего серверного сервера, действителен ли токен и зарегистрирован ли пользователь. И я делаю это с помощью подписки. Проблема в том, что когда я нажимаю «Войти» или «Выйти», компонент не перезагружается. Мне нужно перезагрузить страницу, чтобы она заработала. Каков наилучший способ получить новое значение из подписки без перезагрузки всей страницы? Потому что я не думаю, что перезагрузка всей страницы была хорошей для пользователя.

Это мой код:

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);
  }
}

 

Комментарии:

1. Почему бы не предоставить наблюдаемое состояние входа в систему, а не подписку?

2. @jonrsharpe На самом деле, это мои первые шаги с Angular. Могу ли я решить эту проблему, сохранив наблюдаемую вместо подписки?

Ответ №1:

Причина, по которой вам нужно перезагрузить страницу, чтобы увидеть изменения, заключается в том, что вы обновляете только переменную «isLogged» в ngOnInit.

Я начну с выхода из системы, поскольку я не вижу кода для компонента входа в систему.

Самое простое решение будет выглядеть так:

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

Если вы хотите убедиться, что пользователь вышел из системы, запросив у вас серверную часть, вы можете сделать это следующим образом:

 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)
}
 

Чтобы обновить ваш компонент после входа в систему, вы могли бы создать источник события в вашем компоненте входа в систему и привязаться к нему в панели навигации, которая будет выглядеть следующим образом:

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

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

После этого вам нужно прослушать это событие в вашем navbar.component.html вот так:

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

Или более простая версия:

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

Комментарии:

1. Это работает для меня. Спасибо !!. Просто исправление и вопрос: Исправление: мне нужно было поставить @Output() перед объявлением EventEmitter. Пожалуйста, измените это в решении. Вопрос: Разве не плохая идея подписываться много раз на один и тот же наблюдаемый объект?

2. Ах, извините, не использовал Angular почти полгода, поэтому забыл о @Output декораторе: D. Спасибо за исправление! Вы спрашиваете о this.authService.isLogged() подписке в updateLoginStatus() функции? Это может быть неочевидно, но на самом деле вы получаете новую наблюдаемую при каждом вызове this.authService.isLogged() . После подписки на него он выполняет HTTP-запрос и немедленно завершается после получения ответа (не имеет значения, успешно или нет). Так что вполне нормально подписываться на нее столько раз, сколько вам нужно.