4

I can't get one of my directives to work after refactoring my AngularJS code to use the "controller as" syntax.

Old working code (Fiddle):

<div ng-app='MyModule'>
    <div ng-controller='DefaultCtrl'>
        <input auto-complete ui-items="names" ng-model="selected"/>
        selected = {{selected}}
    </div>        
</div>

JS:

function DefaultCtrl($scope) {
    $scope.names = ["john", "bill", "charlie"];
}

angular.module('MyModule', []).directive('autoComplete', function($timeout) {
    return function(scope, iElement, iAttrs) {
            iElement.autocomplete({
                source: scope[iAttrs.uiItems],
                select: function() {
                    $timeout(function() {
                      iElement.trigger('input');
                    }, 0);
                }
            });
    };
});

Here is my non-working (autocomplete suggestions do not appear) version with "controller as" (Fiddle):

<div ng-app='MyModule'>
    <div ng-controller='DefaultCtrl as ctrl'>
        <input auto-complete ui-items="ctrl.names" ng-model="ctrl.selected"/>
        selected = {{ctrl.selected}}
    </div>
</div>

JS:

function DefaultCtrl() {
    this.names = ["john", "bill", "charlie"];
}

angular.module('MyModule', []).directive('autoComplete', function($timeout) {
    return function(scope, iElement, iAttrs) {
            iElement.autocomplete({
                source: scope[iAttrs.uiItems],
                select: function() {
                    $timeout(function() {
                      iElement.trigger('input');
                    }, 0);
                }
            });
    };
});
5
  • Where do you register your controller in angular? I'm missing somthing like "angular.module('MyModule', []) .controller('DefaultCtrl', DefaultCtrl);" Commented Oct 19, 2014 at 15:56
  • 1
    @John if that was the case, the original code wouldn't work as well. So it's clearly written, but OP chose not to show it here because it's irrelevant. Commented Oct 19, 2014 at 16:06
  • @Shomz you're right, I'm sorry. Commented Oct 19, 2014 at 16:09
  • 1
    @HoffZ did you check you're access to scope? I think you're doing something like scope["ctrl.names"] instead of scope["ctrl"]["names"]? Commented Oct 19, 2014 at 16:11
  • @John No need to be sorry. :) And yes, your second comment is exactly what's wrong with the code (wrote a similar answer at about the same time). Commented Oct 19, 2014 at 16:13

3 Answers 3

4

The issue seems to be with this line:

source: scope[iAttrs.uiItems],

which becomes:

source: scope['ctrl.names'],

which I see why could become problematic if used with object notation.


The fix was to avoid it like this:

source: scope.ctrl[iAttrs.uiItems], // "controller as" lets us have scope namespaces

and changing the attribute name:

ui-items="names"

See the whole thing here: http://jsfiddle.net/cnve0jbh/2/

Sign up to request clarification or add additional context in comments.

1 Comment

Or better, declare uiItems as a directive scope element (scope: {uiItems: '='}), and thus pass the array itself instead of passing the name of the array. jsfiddle.net/kq1z83yk/1
1

As already pointed out by @Shomz the problem is how you try to access ctrl.names. You usually don't want to access the scope like that. Two alternatives:

  • Use an isolated scope:

    .directive('autoComplete', function($timeout) {
      return {
        scope: {
               items: '=uiItems'
             },
        link: function(scope, iElement, iAttrs) {
                iElement.autocomplete({
                source: items,
    
  • Use the $parse service:

    .directive('autoComplete', function($timeout, $parse) {
      return function(scope, iElement, iAttrs) {
                iElement.autocomplete({
                  source: $parse(iAttrs.uiItems)(scope),
    

Comments

1

'Controller As' syntax is just a syntactic sugar over $scope approach to define view model. It just creates an property in the $scope object itself. If you do ng-controller = "MainCtrl as main", it just creates a property main in the $scope object, which is same as the Controller function object.

so in the scope object of link function it can be accessed as $scope.main

As pointed out above, you can use the $parse service or you can use $eval service like this (which internally uses $parse) -

iElement.autocomplete({ source: scope.$eval(iAttrs.uiItems),

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.