Why is 'this' referring to "window" in this TypeScript snippet?

Given this piece of code:

module movieApp {
    export interface IHomeControllerScope extends ng.IScope {
        moviesToDownload: string[];
        active: string;

        deleteMovieFromDownloadList(movie: any);

        markMovieAsDownloaded(movie: any);
    }

    export class HomeController {
        public static $inject = [
            '$scope',
            '$location',
            'MovieService'
        ];

        constructor(private $scope: IHomeControllerScope, private $location: ng.ILocationService, private MovieService) {
            this.$scope.$on('$locationChangeSuccess', (event) => {
                this.setActiveUrlPart();
            });

            MovieService.getMoviesToDownload().then(response => {
                this.$scope.moviesToDownload = response;
            });
        }

        private setActiveUrlPart() {
            var parts = this.$location.path().split('/');
            this.$scope.active = parts[1];
        }

        public get moviesToDownload() {
            return this.$scope.moviesToDownload;
        }

        public markMovieAsDownloaded(movie: any) {
            movie.Downloaded = true;
        }

        public deleteMovieFromDownloadList(movie: any) {
            this.MovieService.deleteMovieFromDownloadList(movie).then(() => {
                debugger;
                this.$scope.moviesToDownload = _.without(this.$scope.moviesToDownload, movie);
            });
        }
    }
}

app.controller("HomeController", movieApp.HomeController);

      

Everything works fine, but in the method deleteMovieFromDownloadList

on the line this.$scope.moviesToDownload = _.without(this.$scope.moviesToDownload, movie);

, it this

refers to the window object, not the actual object I expect.

The generated JavaScript looks like this:

var movieApp;
(function (movieApp) {
    var HomeController = (function () {
        function HomeController($scope, $location, MovieService) {
            var _this = this;
            this.$scope = $scope;
            this.$location = $location;
            this.MovieService = MovieService;
            this.$scope.$on('$locationChangeSuccess', function (event) {
                _this.setActiveUrlPart();
            });

            MovieService.getMoviesToDownload().then(function (response) {
                _this.$scope.moviesToDownload = response;
            });
        }
        HomeController.prototype.setActiveUrlPart = function () {
            var parts = this.$location.path().split('/');
            this.$scope.active = parts[1];
        };

        Object.defineProperty(HomeController.prototype, "moviesToDownload", {
            get: function () {
                return this.$scope.moviesToDownload;
            },
            enumerable: true,
            configurable: true
        });

        HomeController.prototype.markMovieAsDownloaded = function (movie) {
            movie.Downloaded = true;
        };

        HomeController.prototype.deleteMovieFromDownloadList = function (movie) {
            var _this = this;
            this.MovieService.deleteMovieFromDownloadList(movie).then(function () {
                debugger;
                _this.$scope.moviesToDownload = _.without(_this.$scope.moviesToDownload, movie);
            });
        };
        HomeController.$inject = [
            '$scope',
            '$location',
            'MovieService'
        ];
        return HomeController;
    })();
    movieApp.HomeController = HomeController;
})(movieApp || (movieApp = {}));

app.controller("HomeController", movieApp.HomeController);
//# sourceMappingURL=HomeController.js.map

      

As you can see, in the generated JS, the specific method uses _this. It looks right, right?

Can someone explain to me what is happening and how to fix it?

EDIT:

I am using this in conjunction with Angular:

<body data-ng-app="movieApp" data-ng-controller="HomeController as homeCtrl">
  <div class="col-sm-1">
    <i class="fa fa-trash-o" data-ng-click="homeCtrl.deleteMovieFromDownloadList(m)" title="Verwijder uit lijst"></i>
  </div>
</body>

      

EDIT II: After trying all the suggestions and then returning the original code snippet I posted here everything just works great! I don't know how, but I think it has something to do with Chrome / VS 2013. Anyway, thanks to those who tried to help me.

+3


source to share


3 answers


The "deleteMovie ..." function is probably bound to a button or some other UI element. In this case, this function is executed in the context of the window. To fix the problem, you must define the body of the function in the constructor of your controller:

constructor(private $scope: IHomeControllerScope, private $location: ng.ILocationService, private MovieService) {
// other initialization code...

this.deleteMovieFromDownloadList = (movie: any) => {
    this.MovieService.deleteMovieFromDownloadList(movie).then(() => {
        debugger;
        this.$scope.moviesToDownload = _.without(this.$scope.moviesToDownload, movie);
    });
  }
}

      



and declare the corresponding function in the controller class:

deleteMovieFromDownloadList: (movie: any) => void;

      

+2


source


I guess the problem is how it is called deleteMovieFromDownloadList

. If you do something like this:

var myController = new HomeController();
someFramework.doSomethingWithCallback(myController.deleteMovieFromDownloadList);

      

... someFramework

will cause the callback to be called without being bound to the expected context. If so, you can fix it by doing the following:

var boundCallback = myController.deleteMovieFromDownloadList.bind(myController);
someFramework.doSomethingWithCallback(boundCallback);

      

Update:



When reading the Angular docs, it seems that what you put into the click handler is not actually JavaScript, but interpreted by Angular. So it might be homeCtrl.deleteMovieFromDownloadList(m)

doing something like this:

var fn = homeCtrl.deleteMovieFromDownloadList;
fn(m):

      

... which of course won't install correctly this

.

With the controller installed, you can't do this?

data-ng-click="deleteMovieFromDownloadList(m)"

      

+2


source


Javascript has some weird behaviors when it comes to constructor functions. I gave an example to show you how it works:

function myClass() {
    var me = this;
    this.property = 'test';
    this.windowObj = function() {
        windowObjTest();
    }

    var windowObjTest = function() {
        console.log(this); // this = window object
    }

    this.myself = function() {
        myselfTest();
    }

    var myselfTest = function() {
        console.log(me); // me = this object
    }
}

var myobj = new myClass();
myobj.windowObj();
myobj.myself();

      

this.windowObj();

is a public function to call a private function windowObjTest();

. Inside a private function this

is a reference to the window object instead of that instance (don't ask me dev was tall or whatever ...). To get a reference to the current object at a private function, you must install a link to your current object in its function of the design: var me = this;

.

Try an example and take a look at the console. Two entries should appear:

- Window test.php //console.log(this);
- myClass { property="test", windowObj=function(), myself=function()} //console.log(me);

      

+1


source







All Articles