Как использовать ионное хранилище данных с BehaviorSubject и Observable

Я пытаюсь создать приложение с Angular 9 / Ionic 5

Я использую ионное хранилище данных

Итак, мой auth.service.ts выглядит так:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { Storage } from '@ionic/storage'

import { BehaviorSubject, Observable, from } from 'rxjs'

@Injectable({
    providedIn: 'root'
})

export class AuthService {

    private currentTokenSubject: BehaviorSubject<string>
    public currentToken: Observable<string>

    constructor(
        private http: HttpClient,
        private storage: Storage,
    ) {
        this.getToken()
            .then(res => {
                this.currentTokenSubject = new BehaviorSubject(res)
                this.currentToken = this.currentTokenSubject.asObservable()
            }
        )
    }

    async getToken() {
        return await this.storage.get('accessToken')
    }

    public get currentTokenValue(): string {
        return this.currentTokenSubject.value;
    }

    login(username: string, password: string) {
        const headers = new HttpHeaders({
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': 'Basic ' + btoa(username + ':' + unescape(encodeURIComponent(password)))
        })

        return this.http.post<Token>(`${environment.apiUrl}/auth/signin`, { }, { headers })
            .pipe(map((res: Token) => {
                let token = res.token
                // store user details and jwt token in local storage to keep user logged in between page refreshes
                this.storage.set('accessToken', token);
                return token;
            }));
    }

    logout() {
        // remove user from local storage to log user out
        this.storage.remove('accessToken');
        this.currentTokenSubject.next(null);
    }
}

а jwt.interceptor.ts выглядит так:

import { Injectable } from '@angular/core'
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'
import { Observable } from 'rxjs'

import { AuthService } from '@app/_services'

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
    constructor(
        private authService: AuthService
    ) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // add authorization header with jwt token if available
        const currentToken = this.authService.currentTokenValue;

        if (currentToken) {
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${currentToken}`
                }
            });
        }

        return next.handle(request);
    }
}

Итак, когда я пытаюсь вызвать службу, я получаю сообщение об ошибке, потому что Ionic Storage возвращает Observable:

Error: Uncaught (in promise): TypeError: Cannot read property 'value' of undefined
TypeError: Cannot read property 'value' of undefined
at AuthService.get currentTokenValue [as currentTokenValue] (auth.service.ts:39)

Вопрос: как правильно извлечь выгоду из ионного хранилища и использовать его?


person Roman Khegay    schedule 04.06.2020    source источник


Ответы (1)


Проблема, с которой вы пытаетесь получить доступ к геттеру BehaviorSubject value до того, как ему будут присвоены какие-либо значения. Лучше избегать value получателя и подписываться на наблюдаемое, чтобы поддерживать асинхронность. Попробуйте следующее

auth.service.ts

export class AuthService {
  private currentTokenSubject = new BehaviorSubject<string>(null); // <-- assign default value here

  constructor(
    private http: HttpClient,
    private storage: Storage,
  ) {
    this.getToken().then(
      res => {
        this.currentTokenSubject.next(res);               // <-- push token to the observable
      }
    );
  }

  async getToken() {
    return await this.storage.get('accessToken');
  }

  public get currentTokenValue(): Observable < string > {
    return this.currentTokenSubject.asObservable();        // <-- return observable here
  }

  login(username: string, password: string) {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Authorization': 'Basic ' + btoa(username + ':' + unescape(encodeURIComponent(password)))
    })

    return this.http.post<Token>(`${environment.apiUrl}/auth/signin`, {}, { headers }).pipe(
      map((res: Token) => {
        let token = res.token;
        // store user details and jwt token in local storage to keep user logged in between page refreshes
        this.storage.set('accessToken', token);
        this.currentTokenSubject.next(token);              // <-- also push new token here?
        return token;
      }));
  }

  logout() {
    // remove user from local storage to log user out
    this.storage.remove('accessToken');
    this.currentTokenSubject.next(null);
  }
}

Теперь вам нужно подписаться на функцию currentTokenValue(), чтобы получить токен.

Какой-то компонент / услуга

export class SomeComponent implements OnInit {
  token: string;

  ngOnInit() {
    this.authService.currentTokenValue().subscribe(
      token => { 
        if(token) {       // <-- check if token valid because it can also be null
          this.token = token;
        }
      }
    );
  }
}
person Michael D    schedule 04.06.2020
comment
да, но я не могу вызвать подписку внутри auth.guard - person Roman Khegay; 04.06.2020
comment
В этом случае попробуйте сейчас с помощью метода getValue(). Раньше это могло вызвать ошибку, потому что currentTokenSubject не был инициализирован в определении. - person Michael D; 04.06.2020