Search code examples
angularjsangularjs-directiveangularjs-model

How to pass a directive template model to controller / parent scope


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.


Solution

  • 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)