简体   繁体   中英

Unable to pass/update ngModel from controller to directive

I'm using ui-select plugin and I'm passing ng-model from my controller to a custom directive called richSelect but the ng-model doesn't seemed to get updated on select of any item.

<richselect ng-model="dataModel"></richselect>

Custom directive

app.directive('richselect', ['$compile', function ($compile) {
return {
    restrict: 'AE',
    scope: {
        ngModel: '='               /* Model associated with the object */
    },
    link: function (scope, element, attrs, ngModel) {
        scope.options = [
                         {
                             'Value' : 'value1',
                             'Desc' : 'Value One'
                         },
                         {
                             'Value' : 'value2',
                             'Desc' : 'Value Two'
                         }
        ]
        scope.getRichSelectTemplate = function () {

            return '<ui-select multiple ng-model="ngModel" theme="bootstrap"  ng-disabled="disabled">' +
            '{{ngModel}} <ui-select-match placeholder="Select">{{$select.selected.Desc}}</ui-select-match>' +
                '<ui-select-choices repeat="option in options | filter: $select.search">' +
                        '<span ng-bind-html="option.Desc | highlight: $select.search"></span>' +
                '</ui-select-choices>' +
            '</ui-select>';
        }       

         var linkFn = $compile(scope.getRichSelectTemplate())(scope);
         element.append(linkFn);
    }
}
}]);

Plnkr : http://plnkr.co/edit/Im8gpxEwnU7sgrKgqZXY?p=preview

Here, try this. I wasn't exactly sure what format or output you were trying to get, but this gets the selected options passed to the View.

  • EDIT - I got rid of the plunker that used to be here.

You have to use the ngModel.$setViewValue in order to change the value of ng-model in the directive in the view. Additionally, to get the value of the ui-select, you need to have ng-model pointed at the options.selected

Then it was just a matter of adding an ng-click that pointed to a function that updated the view with ngModel.$setViewValue(scope.options.selected .

Also, I believe you need to `require: 'ngModel' in your directive so you can access the ngModelController.

app.directive('richselect', ['$compile', function ($compile) {
    return {
        restrict: 'AE',
        require: 'ngModel',
        scope: {
            blah: '='               /* Model associated with the object */
        },
        link: function (scope, element, attrs, ngModel) {
          scope.changer = function() {
            ngModel.$setViewValue(scope.options.selected)
            console.log(scope.options.selected)
          }
            scope.options = [
                             {
                                 'Value' : 'value1',
                                 'Desc' : 'Value One'
                             },
                             {
                                 'Value' : 'value2',
                                 'Desc' : 'Value Two'
                             }
            ]
            scope.getRichSelectTemplate = function () {

                return '<ui-select multiple ng-model="options.selected" theme="bootstrap" ng-click="changer()" ng-disabled="disabled">' +
                '{{options.selected}} <ui-select-match placeholder="Select">{{$select.selected.Desc}}</ui-select-match>' +
                    '<ui-select-choices repeat="option in options | filter: $select.search">' +
                            '<span ng-bind-html="option.Desc | highlight: $select.search"></span>' +
                    '</ui-select-choices>' +
                '</ui-select>';
            }       

             var linkFn = $compile(scope.getRichSelectTemplate())(scope);
             element.append(linkFn);
        }
    }
}]); 

EDIT: After a lot of digging and tinkering, per the comment below - getting two-way binding working has proved somewhat elusive. I found it was quite easy to do using the standard ui-select directive, as seen here (modified example code from ui-select), because we can easily get access to the scope of the directive: Standard Directive Demo

I also came across a similar wrapper as the one in the OP, but after playing with it,that one seemed to have the same issue - it's easy to get stuff out, but if you need to push data into the directive it doesn't want to go. Interestingly, in my solution above, I can see that the `scope.options.selected' object actually contains the data, it just never gets down the the scope of the ui-select directive, and thus never allows us to push data in.

After encountering a similar issue with a different wrapper directive in a project I am working on, I figured out how to push data down through the different scopes.

My solution was to modify the ui-select script itself, adding an internal $watch function that checked for a variable in it's $parent scope. Since the ui-select directive uses scope: true , it creates a child scope (which, if I am not mistaken, the parent would be the directive in this OP).

Down at the bottom of the link function of the uiSelect directive I added the following watch function:

scope.$watch(function() {
  return scope.$parent.myVar;
}, function(newVal) {
  $select.selected = newVal;
})

In the link function of our directve here, I added this $watch function:

scope.$watch(function() {
  return ngModel.$viewValue;
}, function(newVal) {
  scope.myVar = newVal;
})

So what happens here is that if the $viewValue changes (ie, we assign some data from a http service, etc. to the dataModel binding, the $watch function will catch it and assign it to scope.myVar. The $watch function inside the ui-select script watches scope.$parent.myVar for changes (We are telling it to watch a variable on the scope of it's parent). If it sees any changes it pushes them to $select.selected - THIS is where ui-select keeps whatever values that have been selected by clicking an item in the dropdown. We simply override that and insert whatever values we want.

Plunker - Two-way binding

First of all dataModel is a string. Since you defined multiple the model would be an array.

What's more important is that the uiSelect directive creates a new scope. That means that ng-model="ngModel" does no longer point to dataModel . You effectively destroy the binding.

In your controller make dataModel an object:

$scope.dataModel = {};

In your directive let the selected values be bound to a property:

return '<ui-select multiple ng-model="ngModel.selection"

Now the the selected values will be bound to dataModel.selection .

If you don't use the ngModelController you shouldn't use ng-model with your directive.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM