3

Cannot Use ControllerAs this

Can anyone explain the following scenario to me, please?

Works

ng-controller="Parent as thus"

Breaks

ng-controller="Parent as this"

That single letter which makes it a keyword -- which I want -- wrecks the forest.

Why is this?

P.S. I'm aware of the vm convention, but I find it disturbs portability of controllers/viewmodels.

Cody
  • 9,785
  • 4
  • 61
  • 46
  • 3
    but I find it disturbs portability of controllers/viewmodels ? can you please explain "this"? no pun intended – Parv Sharma Jul 11 '16 at 20:23
  • 2
    I'm guessing Angular tries to evaluate that passed in string, and `this` is a reserved word. – tymeJV Jul 11 '16 at 20:23
  • In terms of architecture, its a bad practice to couple to your base-lib/framework. As so, you may want to port your generic *Viewmodel* from system to system. I find the `vm` approach to incur some degree of change-cost when ports occur. For instance, if I took a generic `CalendarViewmodel` that is used for all projects, I would have to go through all references of `this` and turn it into `vm`; just for that controller. That's the oversimplified example, and its just one of many. A lot more to be said here. – Cody Jul 11 '16 at 20:32

2 Answers2

6

The problem is certainly not that this is a reserved word in JavaScript. There is no rule in the controller as syntax that says you would need to assign the value of this to a variable with the same name as the controller and I'm pertty sure angular won't do such thing either. Why would it? That would be incredibly stupid use of Function constructor and just a needless bug.

There's a simple way to test that JavaScript reserved words are not the issue here. Name your controller "throw". "Parent as throw". throw is a reserved word, but does that throw errors? No. Does that work? Yes.

this is, however, reserved in the context of angular's own template expressions. It's used to refer to the current scope of the expression.

<div ng-controller="Parent as this">
    {{log(this)}}
</div>


angular.module('testApp', []).controller('Parent', function($scope){
    this.test = 'foo';
    $scope.log = function(arg){
        console.log(arg);
    };
});

The above won't throw errors, but it won't log the controller either. Instead, it will log a scope object containing the log function and $parent and what not.

In fact, it will also contain something intresting to us: property this: Object, our controller.

And sure enough, change the template expression to {{log(this.this)}} and it will log the controller instance just fine. Still wouldn't use 'this' as the name though, it would probably just cause more bugs by mistake than undefined functions ever have.

noppa
  • 3,947
  • 21
  • 22
  • 1
    While this is certainly MORE informative, it still doesn't give a definitive answer as to ***why*** AngularJS is refusing to parse the keyword. – David L Jul 11 '16 at 21:03
  • @DavidL What makes you think angular doesn't parse it? In the example above, angular certainly parses the "Parent as this" expression. It just refuses to override the value of word `this` in the template scope. There could be many reasons for that, from design decisions to implementation details. Or are you experiencing actual errors that I just don't know about? – noppa Jul 11 '16 at 21:06
  • "this is, however, reserved in the context of angular's own template expressions". What I'm saying is that this claim would be ideally backed up with the source that demonstrates this. The filter is obviously applied somewhere in the expression syntax and the source for it would be wonderful. Your explanation otherwise is perfectly sound. – David L Jul 11 '16 at 21:10
  • @DavidL, its because Angular *does reserve `this` as a keyword* in order to track scopes. As scopes are chained using `Prototypical Inheritance`, parsing `this` will resolve to the first given object on that chain which contains a property. Its a difference between Scope & Instance. It *does* parse the keyword, but it resolves to the immediate scope, the root-scope, or anything between. – Cody Jul 11 '16 at 21:10
  • In other words, when I was setting `Parent as this` then `{{this.node}}` was broken. However, `{{this.this.node}}` works fine. Its the difference between *transposing* the markup's context with `as name` vs *mapping* namespaces to properties of the markup's current context. I wish angular just took out the scope context and replaced it with my controller instance. – Cody Jul 11 '16 at 21:17
  • @Cody I feel that would have too much unclear and complex behaviour implied there that would cause lots of mistakes and problems for the devs. For example, lots of directives create new scopes, with new values for `this`. Should the "this"-named controller somehow override those child scopes too? If not, you wouldn't be able to refer to the controller from inner scopes, like from inside `ng-for`. If yes, what if some 3rd party library's template uses `this` to refer to its scope (not controller) and your controller name would break it. :( seems problematic – noppa Jul 11 '16 at 21:27
  • 1
    @noppa, I think you're referring to my last sentence there, but that's just wishful thinking. I think they're doing it the best way, actually, in order to *encapsulate what varies*. I think you still need to have the scope/prototype chain, which probably necessitates having `this` as the markup's context. I think we totally agree ;) – Cody Jul 11 '16 at 21:31
0

You are cannot use this as the instance name. this is a reserved keyword.

It would be the equivalent of doing:

var this = new Controller();

You can see that is wrong. It should be something like:

var ctrl = new Controller();

This is essentially what angularJs does behind the scenes when you do controllerAs="Controller as ctrl"

Martin
  • 15,820
  • 4
  • 47
  • 56
  • 1
    Well, I would think what Angular is doing is more like `var hash = {}; hash['this'] = new Controller();` -- which is totally legal syntax. You can indeed have an instance variable referenced as `this.this`. – Cody Jul 11 '16 at 20:37
  • `Controller as ctrl` has internally defined `$scope.ctrl = this;` in the controller's body. – cst1992 Dec 02 '16 at 10:47