【问题标题】:How to reload information from subscrition in Angular?如何在 Angular 中从订阅中重新加载信息?
【发布时间】:2021-02-14 12:26:08
【问题描述】:

在我的导航栏中有一个用于登录和用户选项的下拉菜单。使用 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);
  }
}

【问题讨论】:

  • 为什么不公开一个登录状态的observable,而不是一个订阅?
  • @jonrsharpe 实际上,这是我使用 Angular 的第一步。我可以通过保存 observable 而不是订阅来解决它吗?

标签: angular authentication service frontend


【解决方案1】:

您需要重新加载页面以查看更改的原因是因为您只更新了 ngOnInit 中的“isLogged”变量。

我将从注销开始,因为我没有看到登录组件的代码。

最简单的解决方案如下所示:

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>

【讨论】:

  • 这对我有用。谢谢!!。只是一个更正和一个问题: 更正:我需要将 @Output() 放在 EventEmitter 声明之前。请在解决方案中更改它。问题:多次订阅同一个 Observable 不是一个坏主意吗?
  • 啊,不好意思,快半年没用过Angular了,忘了@Outputdecorator :D。谢谢指正!您在updateLoginStatus() 函数中询问this.authService.isLogged() 订阅?这可能并不明显,但实际上每次调用 this.authService.isLogged() 时都会得到一个 new Observable。订阅它后,它会执行一个 HTTP 请求,并在收到响应后立即完成(无论成功与否)。因此,您可以根据需要多次订阅它。
猜你喜欢
  • 2020-11-16
  • 2013-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 2020-05-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多