I am working with Ionic 4 and I want to show information about the current logged user but is not working I mean it works on any component except the app component, I am using a variable called userData which is a BehaviorSubject. Do you know what is wrong with my code?
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 (I am using getUser() function in order to get the current user information)
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 (here I want to show the user information, the same code is working in every component except this)
<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>
It appears the userData
variable is assigned a new value in functions loadStoredTokens()
and login()
. I don't see login()
function called anywhere and the loadStoreTokens()
function is wrongfully invoked synchronously. It handles data asynchronously, and the data flow should be asynchronous as well.
Service
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();
}
Also if you're using getValue()
function of BehaviorSubject
there isn't any use of the multicast observable. Instead you could skip the getValue()
in the service and subscribe to it in the component.
Service
getUser() {
return this.userData.asObservable();
}
Component
ngOnInit() {
this.auth.getUser().subscribe(
user => { this.user = user }
);
}
Use safe navigation operator ?.
in the template to check if the user
variable is defined before trying to access it's properties. Because initially the value is null
as specified from the default value of the BehaviorSubject
.
<ion-note *ngIf="user">{{ user?.email }} </ion-note>
Or alternatively you could skip the subscription in the component controller and use async
pipe in the template.
Component
ngOnInit() {
this.user = this.auth.getUser();
}
Template
<ion-note *ngIf="user">{{ (user | async)?.email }} </ion-note>