#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-запрос и немедленно завершается после получения ответа (не имеет значения, успешно или нет). Так что вполне нормально подписываться на нее столько раз, сколько вам нужно.