Angular's "controllerAs" not working in routeProvider
Asked Answered
C

4

10

I am trying to use the controllerAs property on a $routeProvider route without any success.

Here is the sample code:

var app = angular.module('app', ['ngRoute']);

app.config(['$routeProvider', '$locationProvider',
  function($routeProvider) {
  $routeProvider
    .when('/', {
      template:'<div>This should be visible:{{ ctrl.one }}</div><div>This should not:{{ one }}</div>',
      controller: 'Ctrl',
      controllerAs: 'ctrl',
    });
}]);

app.controller('Ctrl', function($scope) {
  $scope.one = 'actual';
});

Not sure if this is a bug or I am doing something wrong, This is a plnkr that demonstrates the issue

Calipee answered 14/8, 2014 at 11:9 Comment(0)
R
15

Actual problem:

You seem to be using controllerAs (assigning a value of 'ctrl') originally, but then later not making use of it in the rest of your code. (you used $scope)

enter image description here

Solution:

Working demo as per your sample

When I've been using the controllerAs syntax you need to use one of the following pattern(s) to get access to the instance of the controller:

As opposed to appending the property to $scope, you have to bind to the controller scope. Note this is different to $scope. For lack of a better term, you need to bind controller itself (think of it as its context). As we're dealing with the display layer or view model, I tend to use var vm = this; as a convention, but this personal preference.

[A]: Preferred Solution

app.controller('Ctrl', function() {        
    this.one = 'actual';  
});

//or using 'vm' convention

app.controller('Ctrl', function() {
    var vm = this;
    vm.one = 'actual';  
});

[B]

app.controller('Ctrl', function() {
    var vm = {};
    vm.one = 'actual';
    return vm;
});

Explanation:

When I first started using Angular, [B] was the approach that I used, purely coming from a Knockout background. I was used to binding to a "container" object then binding the object to the view. That being said, I prefer to use [A], or append directly to $scope and forgo the alias completely. Reasons:

  • I feel its cleaner ITO readability
  • As @Swordfish0321 stated, [A] is more performant (should it be a concern to you)
  • I had binding issues with custom directives I wrote that where dependent on certain parent scope properties (specific to my code-base)

Just as a visual:

Demo

 app.controller('Ctrl', function($scope) {
      var vm = this;
      vm.one = 'actual'; 
      console.log($scope) 
 });

Passing in the $scope object and further inspecting it, you'll see a new ctrl child object containing all your public properties and functions that was bound to vm inside the controller code. This is because you've assigned var vm = this. Meaning the vm object in the code is referencing the controller's own scope, which ultimately gets bound to the view. controllerAs basically groups all properties and functions contained internal to the controller to a new object named after the alias that you've provided.

enter image description here

Rattat answered 14/8, 2014 at 11:16 Comment(2)
Why do I need to change the pattern on how to author a controller? This is not the case when "as" is used inline in the markup using: ng-controller="Ctrl as ctrl" what is going on here?Calipee
When you use inline ng-controller="myController as vm", you still need to do the same as my above 2 examples. We don't use ui-router on one of our projects, and the same applies. I personally haven't gone into the the guts of why (Still new-ish to angular myself). My assumption is that the controller creates the $scope for the view, and by using the controllerAs you limit what the view binds/has access to. EG the vm/ctrl object you've returned will be nested inside of the original $scope... if that makes sense. I'll post a small update to my answer to show what I mean.Minier
A
3

To be clear--because I don't think the accepted answer was explicit--the problem with your example is that even though you are assigning a value to controllerAs you are bypassing it by still using $scope.

The "vm" approach stands for view-model which is just a convention, but IMO is far more representative of what is actually going on than "$scope". All we're really trying to do here is bind the view to the view model.

That being said you can technically use both controllerAs AND normal $scope at the same time plunk.

Additionally, the difference between Rohan's examples A and B is that A is the way you should be doing it because you are able to leverage JavaScript's prototypal inheritance which is conducive to better perf. It's also worth noting that because you are now using controllerAs and this you no longer need to inject $scope.

// Functioning Example    
var app = angular.module('app', ['ngRoute']);

app.config(['$routeProvider', '$locationProvider',
  function($routeProvider) {
  $routeProvider
    .when('/', {
      template:'<div>This should be visible:{{ vm.one }}</div><div>This should not:{{ one }}</div>',
      controller: 'Ctrl',
      controllerAs: 'vm',
    });
}]);

app.controller('Ctrl', function() {
  this.one = 'actual';
});
Arbour answered 10/3, 2015 at 19:0 Comment(2)
thanks for clarifying & rephrasing my answer. TBH when I answered originally... I answered from a POV of what worked for me... also being a bit of an Angular newbie at that stage I might have missed the opportunity to explain things a bit better (AH! to have the power of more time and hindsight). I also like to think of myself as a continuous learner of all things JavaScript, I definitely learned a bit from your answer as well, ITO phrasing a solution a certain way... that being said, is it really worth the down-vote though?Minier
Hey Rohan, np. I myself try to learn in a continuous manner. Your original answer was pretty verbose and didn't actually answer the OPs real problem--that was the reason for the down vote. Since you are maintaining the answer in order to improve it, however, I have un-downvoted it. Hopefully that makes sense!Arbour
A
2

The controllerAs is an alias for the controller instance, if you would like to use that, you have to store data into the controller instance this instead of $scope:

app.controller('Ctrl', function($scope) {
  var ctrl = this; // assign to a variable to be consistent when using in the template
  ctrl.one = 'actual';
});

Example Plunker: http://plnkr.co/edit/r8AYtSKbiLQAaPBPriRp?p=preview

Amylose answered 14/8, 2014 at 11:16 Comment(0)
R
2

If you work with $scope, you will have to return $scope at the end of the function for the controllerAs syntax to function.

app.controller('Ctrl', function($scope) {
  $scope.one = 'actual';
    
  return $scope;
});

Good Luck.

Recognize answered 5/7, 2016 at 7:12 Comment(1)
a gem of an answer - if only I found it earlierTetrapody

© 2022 - 2024 — McMap. All rights reserved.