Dawood Awan
Dawood Awan

Reputation: 7328

TypeScript service is undefined when calling a function from common service

I am using TypeScript with Angular JS to write a common service which will handle the POST and GET Requests to a server.

This is what the service looks like:

module TBApp {

    export class apiService {

        static $inject = ['$http', 'notificationService'];

        constructor(private $http, private notificationService: notificationService) {

        }

        get(url, config, success, failure) {

            return this.$http.get(url, config)

                .then(result => { this.handleResponse(result, success); }, result => { this.handleError(result, failure) });
        }

        post(url, data, success, failure) {

            return this.$http.post(url, data)

                .then(result => { this.handleResponse(result, success); }, result => { this.handleError(result, failure) });
        }

        handleResponse(result, success) {

                    this.notificationService.displaySuccess(result.data.message);

                    success(result);

        }

        handleError(result, failure) {

            if (result.status === '401') {

                this.notificationService.displayError('Authentication required.');

                //TODO: redirect to login page

            }
            else if (failure !== null) {
                failure(result);
            }
        }
    }
}

so in the controller shown below I want to call loginCompleted on login success.

module TBApp {

    export class loginController extends MyApp.BaseController {

        membershipService: membershipService;
        apiService: apiService;

        static $inject = ['$scope', 'membershipService', 'apiService', '$location'];

        constructor($scope, membershipService: membershipService, apiService: apiService, private $location) {
            super($scope);

            this.scope.user = new User(null, null);

            this.membershipService = membershipService;
            this.apiService = apiService;
        }

        login() {

            // HERE: this.membershipService Is NOT NULL OR UNDEFINED

            console.log(this.membershipService);


            this.apiService.post('/api/account/authenticate', this.scope.user, this.loginCompleted, this.loginFailed);
        }

        loginCompleted(response) {

            //This method will save the logged in user to cookies

            //HERE : this.membershipService Is UNDEFINED

            this.membershipService.saveCredentials(this.scope.user);

            // redirect to home page

            this.$location.path('/');
        }

        loginFailed(response) {
            alert('login failed');
            console.log(response);
        }

    }
}

The function gets called everything is working except for the this.membershipService is undefined inside the loginCompleted() function.

I think this is because the loginCompleted() function is called from inside the apiService, how can I fix it? what am i doing wrong?

Upvotes: 0

Views: 1309

Answers (2)

Kunso Solutions
Kunso Solutions

Reputation: 7630

@Dawood, you are losing context, my friend. This problem caused because you send functions (loginCompleted, loginFailed) as params, and while they executed their key word this linked to another object.

What you can do here - you can send that functions with binded context (it will be setted always to object which you define as first argument).

module TBApp {

export class loginController extends MyApp.BaseController {

    static $inject: string[] = ['$scope', 'membershipService', 'apiService', '$location'];

    constructor(
        private $scope: ng.IScope,
        private membershipService: membershipService,
        private apiService: apiService,
        private $location
    ) {

        super($scope);

        this.scope.user = new User(null, null);
    }

    login() {

        // HERE: this.membershipService Is NOT NULL OR UNDEFINED

        console.log(this.membershipService);


        this.apiService.post('/api/account/authenticate', this.scope.user, this.loginCompleted.bind(this), this.loginFailed.bind(this));
    }

    loginCompleted(response) {

        //This method will save the logged in user to cookies

        //HERE : this.membershipService Is UNDEFINED

        this.membershipService.saveCredentials(this.scope.user);

        // redirect to home page

        this.$location.path('/');
    }

    loginFailed(response) {
        alert('login failed');
        console.log(response);
    }

   }
  }

In this case you send functions with binded context:

this.loginCompleted.bind(this)
this.loginFailed.bind(this) 

Two new functions will be created with binded context to current class.

More information about this subject: understanding context in js

P.S. Don't blame me I changed code style a bit. Hope my answer will help

UPDATED code below related to comments conversation

module TBApp {

export class loginController extends MyApp.BaseController {

    static $inject: string[] = ['$scope', 'membershipService', 'apiService', '$location'];

    constructor(
        private $scope: ng.IScope,
        private membershipService: membershipService,
        private apiService: apiService,
        private $location
    ) {

        super($scope);

        this.scope.user = new User(null, null);
    }

    login() {

        // HERE: this.membershipService Is NOT NULL OR UNDEFINED

        console.log(this.membershipService);


        this.apiService.post('/api/account/authenticate', this.scope.user,
            (response) => {
                this.membershipService.saveCredentials(this.scope.user);
                // redirect to home page

                this.$location.path('/');
            },
            (response) => {
                alert('login failed');
                console.log(response);
            });
    }

 }
}

Upvotes: 2

Estus Flask
Estus Flask

Reputation: 222494

It should be

this.apiService.post(
  '/api/account/authenticate',
  this.scope.user,
  this.loginCompleted.bind(this), this.loginFailed.bind(this)
)

Alternatively,

this.loginCompleted = this.loginCompleted.bind(this)
this.loginFailed = this.loginFailed.bind(this)

can be done in constructor for all callback/handler methods

Upvotes: 2

Related Questions