4

I'm trying to encapsulate a select in a custom directive while keeping the usage of ng-options on the directive declaration.

The goal is to be able to keep the syntax of the ng-options in the template using my directive, but to correctly forward it to the ng-options of the select inside the directive.

Well more that words, code :

The directive :

    angular.module('myApp').directive("selectField", function() {
    return {
        restrict: 'EA',
        replace: true,
        transclude : false,
        templateUrl : "/common/tpl/form/select-field.html",
        scope : {
            label   : "@",
            model   : "=",
            options : "&"
        }
    };
});

The template :

<div class="form-group">
    <label for="{{fieldId}}" class="col-lg-2 control-label">{{label | translate}}</label>
    <div class="col-lg-10">
        <select class="form-control" id="{{fieldId}}" ng-model="model" ng-options="{{options}}"></select>
    </div>
</div>

The usage :

 <select-field label="myLabel" model="data.mySelectValue" options="p.nom for p in myOptions"></select-field>

And ... the error :

Error: [$parse:syntax] Syntax Error: Token 'for' is an unexpected token at column 7 of the expression [p.nom for p in preteurs] starting at [for p in preteurs].

I tryed with options as "&" , "=" and "@" attributes, but nothing seems to be working.

With "=" and "&", angular explode with the given error, and with "@" attributes, the expression is evaluated in the directive scope, which work but render no options because "myOptions" doesnt exists in the directive's own scope...

Am I missing something ? It there a way to do this ?

Pierre Gayvallet
  • 2,933
  • 2
  • 23
  • 37

3 Answers3

1

No other way to give the select the list and the expression as separated attributes... http://blog.benkuhl.com/2013/08/how-to-use-ng-options-in-a-custom-directive-for-a-dropdown/

Pierre Gayvallet
  • 2,933
  • 2
  • 23
  • 37
1

My solution relies upon the amazing compile directive that I found in the accepted answer at angular ng-bind-html and directive within it ... thanks VKammerrer. It's a bit kludgey, but looking at the blog post you just posted, I think that might be a necessary evil.

The setup is as follows: MyCtrl with MyView, which contains

<select-field compile>

You can, of course, combine select-field and compile into one directive.

MyCtrl has the following defined on the scope:

$scope.myOptions = [
    { value: 0, nom: 'a' },
    { value: 1, nom: 'b' }
];

MyView has the following HTML:

<select-field options="myOptions" data="nom" compile></select-field>

The declaration for selectField is as follows:

angular.module('myApp')
    .directive('selectField',
        function() {
            return {
                restrict: 'E',
                replace: true,
                transclude: false,
                templateUrl: 'scripts/directives/selectField.tmp.html',
                scope: {
                    options: '=',
                    data: '@'
                }
            };
        }
    );

selectField.tmp.html is as follows:

<div>
    <select ng-model="something" ng-options="p.{{data}} for p in options"></select>
</div>

Okay, that seems to work for me, let me know if that helps/doesn't work.

Community
  • 1
  • 1
Robert Balicki
  • 1,583
  • 2
  • 16
  • 24
1

I have found an alternative without involving compiling.

In your custom directive you define 2 bindings:

selectOptions: "<", // One way, it will contain the source array
selectOptionsExp: "@" // Literal string, it will contain the options expression

Then you can use this html in the template:

<select ng-model="..." ng-options="{{ vm.selectOptionsExp }}"></select>

And this is the usage:

<my-directive ng-model="vm.selectedValue"
              select-options="vm.valuesArray"
              select-options-exp="item.label for item in vm.selectOptions track by item.id" />

Note that "vm.selectOptions" is passed in the inner scope, and so is available without resorting to compile or transclude.

Additionally you could simplify the usage with a replaceable token that makes clear to the reader that the variable is not present in the current scope:

vm.getOptionsExp = function() { return vm.selectOptionsExp.replace("$options", "vm.selectOptions"); }
<select ng-model="..." ng-options="{{ vm.getOptionsExp() }}"></select>
<my-directive ng-model="vm.selectedValue"
                  select-options="vm.valuesArray"
                  select-options-exp="item.label for item in $options track by item.id" />

This can be extended to include sorting/filtering custom functions with separate options.

Stefano
  • 61
  • 4