1

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)
    }
}

2 Answers 2

1

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
                            })
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer, it has helped me come up with the best practice IMHO, I will post it now as the answer.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.