为什么我的服务没有在我的应用程序组件上更新?

Why is my service not updating on my app component?

我正在使用 Ionic 4,我想显示有关当前登录用户的信息,但无法正常工作我的意思是它可以在应用程序组件以外的任何组件上运行,我正在使用一个名为 userData 的变量,它是一个 BehaviorSubject。你知道我的代码有什么问题吗?

auth.service.ts

import { Platform } from '@ionic/angular';
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';
import { BehaviorSubject, Observable, from, of, throwError } from 'rxjs';
import { take, map,tap, switchMap, catchError } from 'rxjs/operators';
import { JwtHelperService } from "@auth0/angular-jwt";
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { Tokens } from '../models/tokens';
import { environment } from 'src/environments/environment';

const helper = new JwtHelperService();
//Agregar token access y refresh
const accessToken = 'access';
const refreshToken = 'refresh';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  public user: Observable<any>;
  private userData = new BehaviorSubject(null);
  private access: string = null;
  private refresh: string = null;
  constructor(private storage: Storage, private http: HttpClient, private plt: Platform, private router: Router) { 
    this.loadStoredTokens();  
  }

  getTokenExpirationDate(userData): Date {
    if (userData['exp'] === undefined) return null;

    const date = new Date(0); 
    date.setUTCSeconds(userData['exp']);
    return date;
  }

  isAcessTokenExpired(): boolean {
    let userData = this.userData.getValue();
    console.log(userData);
    if(!userData) return true;

    const date = this.getTokenExpirationDate(userData);
    if(date === undefined) return false;
    return !(date.valueOf() > new Date().valueOf());
  }

  loadStoredTokens() {
    let platformObs = from(this.plt.ready());

    this.user = platformObs.pipe(
      switchMap(() => {
        return from(this.storage.get(refreshToken));
      }),
      map(refreshToken => {
        if (refreshToken) {
          this.refresh = refreshToken;
        } else {
          this.refresh = null;
        }
      }),
      switchMap(() => {
        return from(this.storage.get(accessToken));
      }),
      map(accessToken => {
        if (accessToken) {
          this.access = accessToken;
          let decoded = helper.decodeToken(accessToken); 
          this.userData.next(decoded);
          return true;
        } else {
          this.access = null;
          return null;
        }
      })
    );

  }

  login(credentials: {username: string, password: string }) {
    return this.http
    .post(`${environment.apiUrl}/token/obtain/`,credentials)
    .pipe(
      take(1),
      map(res => {
        // Extract the JWT
        return res;
      }),
      switchMap(token => {
        let decoded = helper.decodeToken(token['access']);
        this.userData.next(decoded);

        from(this.storage.set(refreshToken, token['refresh']))
        let storageObs = from(this.storage.set(accessToken, token['access']));
        return storageObs;
      })
    );

  }

  getUser() {
    return this.userData.getValue();
  }

  logout() {
    this.storage.remove(accessToken).then(() => {

      this.storage.remove(refreshToken).then(() => {
        this.router.navigateByUrl('/');
        this.userData.next(null);
      });

    });
  }

getAccessToken() {
  return  this.access;
}

 getRefreshToken() {
  return this.refresh;
}

setAccessToken(token) {
  this.storage.set(accessToken, token);
}

  refreshToken() {
    return this.http.post<any>(`${environment.apiUrl}/token/refresh/`, {
    'refresh': this.getRefreshToken()
    }).pipe(tap((tokens: Tokens) => {
      this.setAccessToken(tokens.access);
      this.access = tokens.access;
    }));
  }
}

app.component.ts(我使用getUser()函数来获取当前用户信息)

import { Component, OnInit, ViewChildren, QueryList, OnChanges } from '@angular/core';

import { Platform, ModalController, ActionSheetController, PopoverController, IonRouterOutlet, MenuController } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { Router } from '@angular/router';
import { faHome, faInfo, faFileContract } from '@fortawesome/free-solid-svg-icons';
import { ToastController } from '@ionic/angular';
import { AuthService } from './auth/services/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss']
})
export class AppComponent implements OnInit {
  public selectedIndex = 0;
  user = null;

  public appPages = [
    {
      title: 'Home',
      url: '/home',
      icon: faHome
    },
    {
      title: 'About',
      url: '/about',
      icon: faInfo
    },
    {
      title: 'Legal',
      url: '/legal',
      icon: faFileContract
    }
  ];


  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar,
    public modalCtrl: ModalController,
    private menu: MenuController,
    private actionSheetCtrl: ActionSheetController,
    private popoverCtrl: PopoverController,
    private router: Router,
    public toastController: ToastController,
    private auth: AuthService
  ) {
    this.initializeApp();
  }



  initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.backgroundColorByHexString('#D4AF37');
      this.splashScreen.hide();
    });
  }

  ngOnInit() {

    this.user = this.auth.getUser();
    const path = window.location.pathname.split('home/')[1];
    if (path !== undefined) {
      this.selectedIndex = this.appPages.findIndex(page => page.title.toLowerCase() === path.toLowerCase());
    }



  }


}

app.component.html(这里我想显示用户信息,除此之外的每个组件都使用相同的代码)

<ion-app>
  <ion-split-pane contentId="main-content">
    <ion-menu contentId="main-content" type="overlay">
      <ion-content>
        <ion-list id="inbox-list">
          <ion-list-header>Menu</ion-list-header>
          <ion-note *ngIf="user">{{ user.email }} </ion-note>

          <ion-menu-toggle auto-hide="false" *ngFor="let p of appPages; let i = index">
            <ion-item (click)="selectedIndex = i" routerDirection="root" [routerLink]="[p.url]" lines="none" detail="false" [class.selected]="selectedIndex == i">
              <fa-icon (keyup)="onKeyFaIcon($event)" slot="start" [icon]="p.icon"></fa-icon>

              <ion-label>{{ p.title }}</ion-label>
            </ion-item>
          </ion-menu-toggle>
        </ion-list>

      </ion-content>
    </ion-menu>
    <ion-router-outlet id="main-content"></ion-router-outlet>
  </ion-split-pane>
</ion-app>

因为 BehaviorSubject 是异步的,你需要订阅它。

ngOnInit() {
  this.auth.getUser().subscribe(user => this.user = user);
}

还要记住,它不是即时的,所有对 this.user 的依赖也应该是异步的。

例如在模板中你可以勾选

<ng-container *ngIf="user">
  {{ user }} now we can use it
</ng-container>

请使用异步管道来实现。由于用户在应用加载时没有数据。请按照代码进行操作,它可能会对您有所帮助。

app.component.html

<ion-app>
<ion-split-pane contentId="main-content">
<ion-menu contentId="main-content" type="overlay">
  <ion-content>
    <ion-list id="inbox-list">
      <ion-list-header>Menu</ion-list-header>
      <ion-note *ngIf="user">{{ auth?.userData?.email | async }} </ion-note>
    </ion-list>
  </ion-content>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>

keep the email properties in userData object. And keep AuthService scope as public while injection in the app.component.ts constructor.

似乎 userData 变量在函数 loadStoredTokens()login() 中被赋予了一个新值。我没有看到 login() 函数在任何地方被调用并且 loadStoreTokens() 函数被错误地同步调用。它异步处理数据,数据流也应该是异步的。

服务

export class AuthService {
  constructor(private storage: Storage, private http: HttpClient, private plt: Platform, private router: Router) { 
    this.loadStoredTokens().subscribe(
      accessToken => {
        let decoded = helper.decodeToken(accessToken); 
        this.userData.next(decoded);
      },
    );  
  }

  loadStoredTokens() {
    const result = new Subject<any>();
    let platformObs = from(this.plt.ready());

    this.user = platformObs.pipe(
      switchMap(() => {
        return from(this.storage.get(refreshToken));
      }),
      map(refreshToken => {
        if (refreshToken) {
          this.refresh = refreshToken;
        } else {
          this.refresh = null;
        }
      }),
      switchMap(() => {
        return from(this.storage.get(accessToken));
      }),
      map(accessToken => {
        if (accessToken) {
          this.access = accessToken;
          result.next(accessToken);
        } else {
          this.access = null;
          result.next(null);
        }
      })
    );

  return result.asObservable();
}

此外,如果您使用 BehaviorSubjectgetValue() 函数,则不会使用多播可观察对象。相反,您可以跳过服务中的 getValue() 并在组件中订阅它。

服务

getUser() {
  return this.userData.asObservable();
}

组件

ngOnInit() {
  this.auth.getUser().subscribe(
    user => { this.user = user }
  );
}

在模板中使用安全导航运算符 ?. 以在尝试访问其属性之前检查 user 变量是否已定义。因为最初的值是 null,正如 BehaviorSubject.

的默认值所指定的
<ion-note *ngIf="user">{{ user?.email }} </ion-note>

或者您可以跳过组件控制器中的订阅并在模板中使用 async 管道。

组件

ngOnInit() {
  this.user = this.auth.getUser();
}

模板

<ion-note *ngIf="user">{{ (user | async)?.email }} </ion-note>