Tasos
Tasos

Reputation: 7567

Method in Angular 2 is either undefined or not callable

I am working on a personal Angular project. Still trying to learn and move from AngularJS to Angular :)

This is my code:

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

import { LoginService } from './login.service';

@Injectable()
export class LoggedInGuard implements CanActivate {

    constructor(
        private _router: Router,
        private _loginService: LoginService
    ) {
    }

    isLoggedIn() {
            let token = localStorage.getItem('token');
            if ( token ) {
                this._loginService.isLoggedIn(token)
                    .subscribe(v => {
                        return v.status == 'ok';
                    });
            } else {
                return false;
            }
        };

    canActivate() {
        console.log(this.isLoggedIn());

        if (this.isLoggedIn()) {
            return true;
        } else {
            this._router.navigate(['/login']);
            return false;
        };
    }

}

However my problem is:

  1. If I keep this. on the console.log(this.isLoggedIn()); then "undefined" is printed out.
  2. If I remove it, then I have the following message on VS Code [ts] Cannot find name 'isLoggedIn'. Did you mean the instance member 'this.isLoggedIn'? and the same on console Uncaught (in promise): ReferenceError: isLoggedIn is not defined
  3. The method isLoggedIn() is used without a problem from other files where I call it.

Upvotes: 3

Views: 2508

Answers (2)

Tyler Jennings
Tyler Jennings

Reputation: 8911

The reason you are getting undefined on the method:

isLoggedIn() {
    let token = localStorage.getItem('token');
    if ( token ) {
        this._loginService.isLoggedIn(token)
              .subscribe(v => {
                   return v.status == 'ok';
         });
     } else {
           return false;
      }
};

Is because the Subscription this._loginService.isLoggedIn(token) is asynchronous and does not return immediately. So the method isLoggedIn() finishes executing and returns before there is a response.

What you could do instead and what I do is the have your _loginService maintain a local variable that other components can read and consume or a function that can synchronously check the token to make sure it's valid. If a token exists, then a user has logged in, and what you can do from there is check the token to make sure it hasn't expired. That could be done a couple different ways. If your using the angular2-jwt library to help handle your JWT token you could have a method in your _loginService that looks something like this:

authenticated() {
    // Check if there's an unexpired JWT
    return tokenNotExpired();
}

If you aren't using the angular-jwt library to help manage the JWT token, you could do something like this:

isTokenValid(){
    let token = localStorage.getItem('token');
    let decodedToken = window.atob(token.split('.')[1]);

    if((decodedToken.exp * 1000) < Date.now()){
       return false;
    }
    return true;
}

Hope that helps.

Upvotes: 2

Salil Junior
Salil Junior

Reputation: 424

This may not qualify as an answer but I do not have enough reputation points to post a comment - so everyone, pardon me :)

I think you cannot return a value from subscribe like that as it is an async call..

Instead, your isLoggedIn() method should be returned as shown below:

isLoggedIn() {
            let token = localStorage.getItem('token');
            if ( token ) {
                return this._loginService.isLoggedIn(token)
                    .subscribe(v => {
                        return v.status == 'ok';
                    });
            } else {
                return false;
            }
        };

Note the change is here: return this._loginService.isLoggedIn(token)... Hope this helps.

EDIT: Ignore my answer above... That would still not work :( sorry... What's happening is, you are calling isLoggedIn() which executes an asynchronous call (ie a call that will continue executing in the background and return later). That is why you are getting an 'undefined'. What I would do is follow Tyler's advice above and have a local variable in which you store the returned value in and utilize later OR you could pass in a callback to isLoggedIn() method - this callback will be invoked once the async call is done. See below:

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

import { LoginService } from './login.service';

@Injectable()
export class LoggedInGuard implements CanActivate {

    constructor(
        private _router: Router,
        private _loginService: LoginService
    ) {
    }

    isLoggedIn(callback: (v) => void) {
            let token = localStorage.getItem('token');
            if ( token ) {
                this._loginService.isLoggedIn(token)
                    .subscribe(v => {
                        callback(v)
                    });
            }
        };

    canActivate() {
        this.isLoggedIn((v) => { // this here is your callback function which will get invoked once async call is done
            if(v.status == 'ok' {
                // do something... 
                return true;
            } else {
               // do something 
               this._router.navigate(['/login']);
            }
        });
    }

}

Upvotes: 1

Related Questions