1

Ugh I'm stuck in one of those Angular binds (no pun intended) where I can't get my controller to talk to my directive.

My directive is the following, a select dropdown with a template:

app.directive('month', function() {
  return {
    replace:true,
    scope:{
     months:"=",
     monthChoice:"="
   },
    template:'<select ng-model="monthChoice" ng-options=\"currMonth for currMonth in months\" class=\"monthsClass\"></select>',
    link: function (scope, element, attrs) {

      var lastEntry = scope.months.length - 1;
      scope.monthChoice = scope.months[lastEntry];

      scope.$watch('monthChoice', function() {
        console.log(scope.monthChoice);
      });
    }
  }
})

The months values that populate the select are coming from a service that communicates to the controller:

app.controller('CandListCtrl', ['$scope', 'Months',
  function ($scope, Months) {

    $scope.months = Months.init();

    $scope.$watch('monthChoice', function() {
        console.log($scope.monthChoice);
      });

    $scope.whichMonth = function(m) {
      console.log(m);
      console.log($scope.month);
      return true
    }

  }]);

What I would like to be able to do is to pass the value of the model monthChoice to the controller when a change occurs. That way, I can access it from other html elements in my partial view. My partial view is set up as follows:

<month months="months" ng-change="whichMonth(monthChoice)"></month><br>

It is inside a partial that is routed using a typical $routeProvider:

app.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/', {
        templateUrl: 'partials/cand-list.html',
        controller: 'CandListCtrl'
      }).
      otherwise({
        redirectTo: '/'
      });
  }]);

I am throwing the following error: Expression 'undefined' used with directive 'month' is non-assignable!

And I am unable to access the value from the controller.

Phil
  • 157,677
  • 23
  • 242
  • 245
Union find
  • 7,759
  • 13
  • 60
  • 111
  • Why are you escaping double-quotes in your directive template? – Phil Feb 22 '15 at 23:00
  • I thought that was just the way to go. I deleted; still stuck though. – Union find Feb 22 '15 at 23:00
  • Is there really a point to your directive? It doesn't seem to do much and what it does do seems more suited to a controller (ie, selecting the last option by default) – Phil Feb 22 '15 at 23:05
  • Its point is to provide a select that can talk to other views and aspects of the scope. – Union find Feb 22 '15 at 23:06

2 Answers2

2

This is more of a comment, but it's awkward to post code there. I wonder why you are specifying monthChoice here

scope:{
     months:"=",
     monthChoice:"="
   }

And then trying to reassign it in your link function?

edit: I think I would just use the Months service you already have and set the monthChoicce variable there. It then gets easy to reuse in other controllers and what have you:

app.directive('month', function(Months) {
  return {
    replace:true,
    scope:{
     months:"="

   },
    template:'<select ng-model="service.monthChoice" ng-options=\"currMonth for currMonth in months\" class=\"monthsClass\"></select>',
    link: function (scope, element, attrs) {

      var lastEntry = scope.months.length - 1;
      scope.service = Months;

      scope.$watch('monthChoice', function() {
        console.log(scope.monthChoice);
      });
    }
  }
})

Edit: Did an update. You have to bind to an object, in this case I use the service object itself, for the changes to be reflected throughout. This is because when you set a javascript object to another object, they get linked by reference.

Even more edit

Ok, here's another try that matches more of what you were trying to do initially. No service or other magic. Here is the controller:

.controller('myController', function($scope) {
    $scope.months = [
        'Jan',
        'Feb',
        'Mar'
    ];
    $scope.monthChosen = '';
})

Just a simple array of months, and our monthChosen variable, all of which we will send to the directive. monthChosen will automatically be changed through ng-model and the two-way binding. Here is our route template:

<div data-ng-app="myApp" data-ng-controller="myController">
    <div data-my-directive months="months" month-choice="monthChosen"></div>
    <div>{{monthChosen}}</div>    
</div>

You can see monthChosen being displayed, outside of the directive. When you change the dropdown, it changes. Here is our directive:

.directive('myDirective', function(){
    function link(scope, elem, attrs){
        //You can do fun stuff here if you want        
    }
    return {
        scope: {
            months: '=',
            monthChoice: '='
        },
        link: link,
        template: '<select ng-model="monthChoice" ng-options="currMonth for currMonth in months" class="monthsClass"></select>'
    }
});

It doesn't contain anything other than our template and scope definitions at this point. As you see, you don't have to set monthChoice by yourself, as angular takes care of this :)

jsFiddle: http://jsfiddle.net/vt52bauu/

monthChosen (controller) <=> monthChoice (directive)

Kjell Ivar
  • 1,154
  • 8
  • 8
  • I thought that created two-way databinding, so I can set a value in the directive and have it available in my controller. Then on ng-change, the new value is available. – Union find Feb 22 '15 at 23:08
  • 1
    Yes, I'm not entirely sure why you get that error. I think it should work in theory. I usually use attributes like that when I want to inject something into the directive that it should use... Not when I want the directive to change something in the controller. But in theory, I think that should work too... – Kjell Ivar Feb 22 '15 at 23:13
  • I am very confused as to why you've made Months an argument. Shouldn't it be injected into the directive? I also don't see how this makes anything accessible in the controller. – Union find Feb 22 '15 at 23:17
  • I can try making a quick fiddle to try to explain it further – Kjell Ivar Feb 22 '15 at 23:18
  • Thank you. I tried to fiddle but Angular and fiddle don't play so nice with templates. – Union find Feb 22 '15 at 23:20
  • Thanks for the edit. Can you post how you would access the service object in the controller please? – Union find Feb 22 '15 at 23:20
  • After reading over your question again, maybe using a service isn't the answer. You're only going to use monthChoice other places on the same route? – Kjell Ivar Feb 22 '15 at 23:30
  • Right. The idea is to use monthchoice as a model to filter other aspects of the view. – Union find Feb 22 '15 at 23:31
  • 1
    Ok, I'll post a simple example. Your original thought here should work, but you don't really need to set a watch, as the change will automatically be reflected through ng-model and the two-way binding in the directive – Kjell Ivar Feb 22 '15 at 23:33
  • 1
    You could also drop the scope definition object from the directive. The directive then won't have it's own scope, and can access the controller's scope directly. This of course limits the reusability of the directive across controllers. – Kjell Ivar Feb 22 '15 at 23:46
1

You could simply use both your directive and ngModel and communicate via the ngModel.NgModelController. For example...

app.directive('month', function() {
  return {
    restrict: 'E',
    replace: true,
    require: 'ngModel',
    scope: {
      months: '='
    },
    template: '<select class="monthsClass" ng-options="month for month in months"></select>',
    link: function(scope, element, attr, modelController) {
      modelController.$setViewValue(scope.months[scope.months.length - 1]);
      modelController.$render();
    }
  }
});

and

<month ng-model="month" months="months"></month>

Plunker

Phil
  • 157,677
  • 23
  • 242
  • 245
  • This seems like the simplest solution in retrospect. Though I ended going with a non-directive solution that used a controller to populate the selects. I had hoped to move the selects to directives but for now this works fine. – Union find Feb 23 '15 at 00:25