Reputation: 290
I'm new to Observables. I'm using them to check if the user is logged in or not in the constructor of my root page.
I have nested many Observables in order to do this, and they are working fine, but I think that there is a way to flatten all of these nested Observables.
I just want you to review the code and let me know what could be improved, and if I can flatten these Observables in login-page.ts
pages/login-page/login-page.ts
export class LoginPage {
constructor(public navCtrl: NavController, private userService: UserService, private storage: Storage) {
this.userService.getStoredToken().subscribe(
data => {
console.log('Token and username are stored.')
this.userService.checkTokenValidity(data[0], data[1]).subscribe(
() => {
console.log('Token and username and valid.')
// Go to the homepage
this.navCtrl.push(TabsPage)
}, err => {
console.log("Invalid token, trying the stored username and password.")
this.userService.getStoredUserAndPassFromStorage().subscribe(data => {
console.log('Successfuly retrieved the username and password')
this.userService.login(data[0], data[1]).subscribe((res) => {
console.log('Username and password are valid.')
// Go to the homepage
this.navCtrl.push(TabsPage)
// Save new user data to local storage
this.userService.authSuccess(res.access_token, data[0], data[1])
}, err => {
console.log("Failed to login using the stored username and password.")
//Remove the loading and show login form
})
}, err => {
console.log("No stored token.")
//Remove the loading the and login form
})
}
)
},
err => {
//Remove the loading the show login form
}
)
}
providers/user-service.ts
export class UserService {
loginDetails: ILogin
headers: any
error: string
apiUrl = global.apiUrl
loginUrl = api.loginUrl
contentHeader: Headers = new Headers({'Content-Type': 'application/json'})
constructor(public http: Http, private storage: Storage) {
}
logout() {
this.storage.remove('_user')
this.storage.remove('_pass')
this.storage.remove('_token')
}
login(username: string, password: string): Observable<IAccessToken> {
this.loginDetails = {
client_id: global.clientId,
client_secret: global.clientSecret,
grant_type: 'password',
username: username,
password: password,
}
let body = JSON.stringify(this.loginDetails)
let options = new RequestOptions({headers: this.contentHeader})
return this.http
.post(this.loginUrl, body, options)
.map(response => response.json())
}
getStoredToken(): Observable<string[]> {
return Observable.forkJoin(
this.storage.get('_token'),
this.storage.get('_user')
)
}
getStoredUserAndPassFromStorage(): Observable<string[]> {
return Observable.forkJoin(
this.storage.get('_user'),
this.storage.get('_pass')
)
}
checkTokenValidity(token: any, username: any): Observable<IAccessToken> {
let params = new URLSearchParams()
params.set('access_token', token)
params.set('_format', 'json')
return this.http.get(api.userInfoUrl(username), {
search: params
}).map(response => response.json())
}
authSuccess(access_token, username, password) {
this.error = null
this.storage.set("_user", username)
this.storage.set("_pass", password)
this.storage.set("_token", access_token)
}
}
Upvotes: 1
Views: 662
Reputation: 290
Thanks Alexander for pointing out the benefit of switchMap
in this case.
I've used your solution, and I have done some refactoring on top of it to make sure it's as readable as possible.
export class LoginPage {
login: ILogin
constructor(public navCtrl: NavController, private userService: UserService, private storage: Storage) {
this.forgotPasswordPage = ForgotPasswordPage
this.checkIfUserIsLoggedIn();
}
checkIfUserIsLoggedIn() {
const storedToken$ = this.userService.getStoredToken()
const checkTokenValidity$ = (data) => this.userService.checkTokenValidity(data[0], data[1])
const storedUserAndPass$ = this.userService.getStoredUserAndPassFromStorage();
const login$ = (data) => this.userService.login(data)
const checkStoredTokenValidity$ = storedToken$.switchMap(data => checkTokenValidity$(data))
const loginUsingStoredUsernameAndPass$ = storedUserAndPass$.switchMap(data => login$(data))
checkStoredTokenValidity$.subscribe(() => {
this.navCtrl.push(TabsPage)
}, () => {
console.log('Invalid token, now trying the saved username and password');
loginUsingStoredUsernameAndPass$.subscribe(res => {
this.navCtrl.push(TabsPage)
this.userService.updateToken(res.access_token)
}, () => {
console.log('Invalid stored username and pass, they possibly just got changed.')
//Remove the loading animation to show the login form.
})
})
}
}
IMHO this is the best version of code I was able to generate based on switchMap
If someone can think of a better version, please suggest it.
Upvotes: 0
Reputation: 10824
To pass parameters and start a new Observable from an Observable you can use the switchMap
operator.
In your case this would be
this.userService.getStoredToken().switchMap(data =>
this.userService.checkTokenValidity(data[0], data[1]).switchMap(isvalid =>
this.userService.getStoredUserAndPassFromStorage().switchMap(data =>
this.userService.login(data[0], data[1]).subscribe((res) => {
console.log('Username and password are valid.')
// Go to the homepage
this.navCtrl.push(TabsPage)
// Save new user data to local storage
this.userService.authSuccess(res.access_token, data[0], data[1])
}, err => {
console.log("Failed to login using the stored username and password.")
//Remove the loading and show login form
})
Upvotes: 1