angularjsangularjs-ng-optionsangularjs-components

How to pass ng-options comprehension_expression to AngularJS component?


I am trying to add a new binding to existing AngularJS component that should take in value of type comprehension_expression as explained in the ng-options Directive API Reference.

Please check the code at the bottom to understand the situation. Note that the top <select> control comes through component named selectField. It does not show any select-options. The bottom control is added directly to index.html and works properly.

I would appreciate if someone can tell me if there is a bug in my script, any alternate approaches to pass value to ng-options attribute to the template, or let me know that there is no way for a component or directive to have such bindings.

angular.module('myApp', [])
  .controller('MainController', function MainController() {
    this.colors = ['red', 'blue', 'green'];
    this.myColor = this.colors[1]; // blue
  }).component('selectField', {
    template: `
      <select ng-model="$ctrl.inputModel"
              ng-options="{{::$ctrl.inputOptionsExpression}}">
      </select>
      Selected: {{$ctrl.inputModel}}</span>
    `,
    bindings: {
      inputModel: '=',
      inputOptionsExpression: '@'
    }
  });
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>

<body ng-app="myApp">
  <div ng-controller="MainController as vm">
    <div>
      <select-field input-model="vm.myColor"
                    input-options-expression="color for color in vm.colors">
      </select-field>
    </div>
    <div>
      <select ng-model="vm.myColor" 
              ng-options="color for color in vm.colors">
      </select>
      Selected: {{vm.myColor}}
    </div>
  </div>
</body>

</html>


Solution

  • See Why mixing interpolation and expressions is bad practice.

    In this case the ng-options directive will parse the comprehension expression before the interpolation directive renders the desired expression.

    Re-write the component to input the choices:

    app.component('selectField', {
        require: {ngModelCtrl: 'ngModel'},
        bindings: {
          ngModel: '<',
          choices: '<'
        },
        template: `
          <select ng-model="$ctrl.ngModel"
                  ng-change="$ctrl.render($ctrl.ngModel)"
                  ̶n̶g̶-̶o̶p̶t̶i̶o̶n̶s̶=̶"̶{̶{̶:̶:̶$̶c̶t̶r̶l̶.̶i̶n̶p̶u̶t̶O̶p̶t̶i̶o̶n̶s̶E̶x̶p̶r̶e̶s̶s̶i̶o̶n̶}̶}̶"̶ ̶
                  ng-options="c for c in choices">
          </select>
          Selected: {{$ctrl.ngModel}}</span>
        `,
        controller: function() {
            this.render = (value) => {
                this.ngModelCtrl.$setViewValue(value);
            };
        }
    })
    

    Usage:

    <select-field ng-model="vm.myColor" choices="vm.colors">
    </select-field>
    

    The DEMO

    angular.module('myApp', [])
    .controller('MainController', function MainController() {
        this.colors = ['red', 'blue', 'green'];
        this.myColor = this.colors[1]; // blue
    })
    .component('selectField', {
        require: {ngModelCtrl: 'ngModel'},
        bindings: {
          ngModel: '<',
          choices: '<'
        },
        template: `
          <fieldset>Select field
          <select ng-model="$ctrl.ngModel"
                  ng-change="$ctrl.render($ctrl.ngModel)"
                  ng-options="c for c in $ctrl.choices">
          </select>
          Selected: {{$ctrl.ngModel}}
          </fieldset>
        `,
        controller: function() {
            this.render = (value) => {
                this.ngModelCtrl.$setViewValue(value);
            };
        }
    })
    <script src="//unpkg.com/angular/angular.js"></script>
    <body ng-app="myApp" ng-controller="MainController as vm">
        <div>
          <select-field ng-model="vm.myColor"
                        choices="vm.colors">
          </select-field>
        </div>
        <div>
          <select ng-model="vm.myColor" 
                  ng-options="color for color in vm.colors">
          </select>
          Selected: {{vm.myColor}}
        </div>
    </body>