0

For a project I'm working on, I've uncovered a situation that doesn't make a whole lot of sense. I strongly suspect that, while I'm working in TypeScript, that my particular problem is due to a funny nuance of javascript, but I'm not sure how to prove it, or fix it.

Background
For the application I'm working on, I have a fairly basic service, that's responsible for communicating with a Web API.

The only thing that's different from what I normally do, is that instead of using TypeScript's 'lambda' syntax for a $http.get(...).then(...) success callback, I'm using class functions instead, because A) the code I'm using is actually using the error callback, and B) the $http.get(...).then(success, error) syntax is a bit messy to read with the lambda syntax.

// Not pretty, but sufficient.
this.$http.get(...).then(() => { ... });

// Ewwww...
this.$http.get(...)
    .then(() => {
        ...
    }, () => {
        ...
    });

// Much better!
this.$http.get(...)
    .then(onFooSuccess, onError);

Here's the relevant service:

namespace MyApp.Models {
    export class WebApiResult<T> {
        public data: T;
    }
}

namespace MyApp.Services {
    export class SomeService {
        public status: SomeServiceStatus = new SomeServiceStatus();

        public static $inject: string[] = ['$http'];
        public constructor(
            public $http: ng.IHttpService
        ) {
        }

        public doSomething(): ng.IPromise<SomeServiceStatus> {
            this.$http.get('/api/some/SomeAction')
                .then(this.onDoSomethingSuccess, this.describeError);
        }

        // This is the problem method.  When this code runs,
        // a type error is generated.
        public onDoSomethingSuccess(result: Models.WebApiResult<SomeServiceStatus>): SomeServiceStatus | ng.IPromise<SomeServiceStatus> {
            if(!result.data.isInSpecialState) {
                return result.data;
            }

            // TypeError!  Can't assign to undefined.
            this.status = result.data;

            return result.data;
        }

        public describeError(error: any) {
             alert('oops');
        }
    }

    export class SomeServiceStatus {
        public isInSpecialState: boolean = false;
        public someStatusMessage: string = '';
        public someIdentifier: string = '';
    }

    angular
        .module('app')
        .service('someService', SomeService);
}

Problem
So, whenever you call this service, the $http get is performed successfully. It's the success callback that's the problem, however - when the line this.status = result.data is hit, it always throws an exception due to being unable to assign result.data to property status of undefined.

At present, my theory is that this does not actually refer to the SomeService, but rather something else, possibly even the class method being used as the delegate.

Questions
This theory leads to some questions.

  1. What exactly is this referring to? Mousing over it in Visual Studio 2015 shows the 'helpful' text: this: this. Thanks Microsoft.
  2. Should this even be doing this? Is this a TypeScript bug, or is this merely me shooting myself in the foot with the generated JavaScript?
  3. Are there any recommended stylistic choices when dealing with an AngularJS promise that has both a success and error callback? It might be that I'm not seeing a better way to write the code, or that I simply don't know something. It wouldn't be the first time that asking a SO question has taught me something.
Andrew Gray
  • 3,563
  • 3
  • 30
  • 64
  • Did you look at any other questions tagged with `this` and `typescript`/`javascript` ? – Ryan Cavanaugh Dec 13 '17 at 21:44
  • I did search before writing the question, but I didn't find anything that addressed this case. Also, unless you're a very fast reader, I'm not sure you even read my question. – Andrew Gray Dec 13 '17 at 21:46
  • 1
    you don't have to read the whole question to realize what the problem is. it's rather obvious. – Kevin B Dec 13 '17 at 21:49
  • Try to change it from: ` public doSomething(): ng.IPromise { this.$http.get('/api/some/SomeAction') .then(onDoSomethingSuccess, describeError); } ` to: ` public doSomething(): ng.IPromise { this.$http.get('/api/some/SomeAction') .then((result) => this.onDoSomethingSuccess(result), describeError); } ` – Taras Ilyin Dec 13 '17 at 21:50
  • If it were obvious to me, I would not ask the question. Please expound, and also, please don't be condescending? – Andrew Gray Dec 13 '17 at 21:50
  • 1
    You passed a function to .then(). that function will then be exeucted in a different context than the one that would allow it access to the `this` that you wanted it to have access to. You can solve it by either passing an anonymous function instead that then calls the method, or, you can bind it. – Kevin B Dec 13 '17 at 21:51
  • It's not going to be obvious to you, but it is obvious to people who have seen dozens of SO questions about `this` in JavaScript, which is why your skepticism about me reading it is unwarranted. This has been covered by hundreds of questions and yours is not substantially different from any of them, which is why I ask whether or not you've looked at other `this` questions – Ryan Cavanaugh Dec 13 '17 at 21:52
  • https://stackoverflow.com/questions/20627138/typescript-this-scoping-issue-when-called-in-jquery-callback – Ryan Cavanaugh Dec 13 '17 at 21:56
  • KevinB - Thank you, your last two comments were much more clear. I've - somehow - never really stubbed my toes on this aspect of how `this` works in the past. I think between those two comments, that is probably the answer. Also, I didn't mean to antagonize - I was literally just asking a question. That being said, the way the responses were leveled in my opinion were unhelpful and unnecessarily harsh. Can we agree to communicate in a more neutral way, even on questions that apparently annoy us? – Andrew Gray Dec 13 '17 at 21:58

1 Answers1

2

It is a JavaScript's thing. If you provide a function instance, or write in classic JavaScript like this:

..then(function(data) { ...}, function(error) { ... })

then, unlike with the new lambda expressions, JavaScript behaves the old way and "this" is set to point at the scope inside of doSomething() {} for those handlers.

But bind() can help you keep your pretty syntax intact, with a slight redundance:

public doSomething() {
    this.$http.get('/api/some/SomeAction')
        .then(this.onDoSomethingSuccess.bind(this), this.describeError.bind(this));
}
Mikser
  • 609
  • 6
  • 15