简体   繁体   中英

Angular Directive with Optional Attributes

I have a custom dropdown directive that has common attributes such as class and ng-model .

I have decided to extend this control for support for validation and now need to include optional attributes that should only get included in the output template if they are set by the programmer.

Sample

指令调用示例

I have a partially working system in which I moved my code out of a template URL and into a string concatenation which I call in the post: function of the directives compile.

I would have preferred to leave my directives HTML in a template, but could not get that working so I have this solution.

Questions:

  1. Is this the best way to write a template with dynamic attributes?
  2. Could this be made to work while leaving the HTML in a template URL
  3. Should I be using the compile => post function or should this be done in the link function

Code for Directive

'use strict';

angular.module(APP)
  .directive('wkKeyLabelSelect', ["$compile",
    function($compile) {
      return {
        restrict: 'EA',
        replace: true,
        scope: {
          'class': '@',              // Permanent - One Way Attribute
          ngModel: '=',              // Permanent - Two Way Attribute (Angular)
          items: '=',                // Permanent - Two Way Attribute (Custom)
          id: '@',                   // Dynamic - One Way Attribute
          name: '@',                 // Dynamic - One Way Attribute
          ngRequired: '=',           // Dynamic - Two Way Attribute (Angular) 
      },
        //templateUrl: COMPONENTS_PATH + '/keyLabelSelect/keyLabelSelect.html',
        controller: 'KeyLabelSelectController',
        link: function (scope, element, attrs) {
          //$compile(element)(scope);
        },
        compile: function (element, attrs) {

          // name & ngRequired are not available in the compile scope
          //element.replaceWith($compile(html)(scope));

          return {
            pre: function preLink(scope, iElement, iAttrs, controller) {

            },
            post: function postLink(scope, iElement, iAttrs, controller) {

              // Template goes here
              var html =
                '<select ' +
                  ' class="{{class}}"' +
                  (scope.id ? ' id="{{id}}"' : "") +
                  (scope.name ? ' name="{{name}}"' : "") +
                  (scope.ngRequired ? ' ng-required="true"' : "") +
                  ' ng-model="ngModel"' +
                  ' ng-options="item.key as item.label for item in items"' +
                  '>' +
                '</select>';

              iElement.replaceWith($compile(html)(scope));
            }
          }
        }
      };
    }
  ]);

Code for Directive Controller

angular.module(APP)

.controller('KeyLabelSelectController', ['$scope', function ($scope) {

  $scope.klass = typeof $scope.klass === 'undefined' ? 'form-control' : $scope.klass;

  console.log($scope.ngModel);
  console.log($scope.items);

}]);

HTML used to run the directive

<div class="form-group" ng-class="{ 'has-error': editForm.state.$touched && editForm.name.$invalid }">
    <label class="col-md-3 control-label">State</label>
    <div class="col-md-9">
        <wk-key-label-select id="state" name="state"
                                ng-required="true"
                                ng-model="model.entity.state"
                                class="form-control input-sm"
                                items="model.lookups.job_state">
        </wk-key-label-select>

        <div class="help-block" ng-messages="editForm.state.$error">
            <p ng-message="required">Job State is required.</p>
        </div>
    </div>

</div>

My Original Template URL content, not used currently

<!-- This is now deprecated in place of inline string -->
<!-- How could I use a in place of string concatenation  -->

<select class="{{klass}}"
        name="{{name}}"
        ng-model="ngModel"
        ng-options="item.key as item.label for item in items"></select>

问题

The "proper" way to introduce a custom input controller is to support the ngModelController . This enables your custom control to integrate with other directives that support ngModel , like custom validators, parsers, <form> s. This is a bit tricky, but makes your control indistinguishable from built-in controls for the framework:

.directive("customSelect", function() {
  return {
    require: "?ngModel",
    scope: {
      itemsExp: "&items" // avoids the extra $watcher of "="
    },
    template: '<select ng-model="inner" \
                       ng-options="item.key as item.label for item in itemsExp()"\
                       ng-change="onChange()"></select>',
    link: function(scope, element, attrs, ngModel) {
      if (!ngModel) return;

      // invoked when model changes
      ngModel.$render = function() {
        scope.inner = ngModel.$modelValue;
      };

      scope.onChange = function() {
        ngModel.$setViewValue(scope.inner);
      };
    }
  };
});

Then, it can neatly integrate with other controls and leverage validators likes ng-required natively:

<custom-select name="c1" ng-model="c1" items="items" ng-required="true">
</custom-select>

Demo

It may not seem like the answer to the question you asked, but that is only because your question is a bit of an XY question. By implementing a custom input control, you achieve what you set out to do - assign name attribute to a directive (which registers itself with the form directive, if it is provided) and ng-required works natively. However, if you must assign name / id to the underlying <select> (for CSS reasons or whatnot), you could use ng-attr- to conditionally apply an attribute. The template would change to:

<select ng-attr-name="attrs.name || undefined"
        ng-attr-id  ="attrs.id   || undefined"
        ng-model="inner" ...

Of course, you'd need to expose attrs on the scope in the link function with:

link: function(scope, element, attrs, ngModel){
  scope.attrs = attrs;

  // etc...
}

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