Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiling dynamic content - AngularJS

I'm rewording this question as I think the original wasn't too clear.

Basically, I have a 'wrapper' directive where I am attempting to dynamically add attributes to one of the wrapped (transcluded) elements. I can get this to work, but Angular doesn't seem to be aware of the new attributes once added.

If I use $compile then Angular does recognise them - but at the expense of double-compiling the transcluded content, and in this case it then doubles the number of options in the select tag.

Here is a plunker that shows (with comments) what I am attempting, and the same code follows below for those who can look at code and suggest an answer just by looking: (note - my ultimate aim is to check the custom directive valid-form-group for the required attribute, and if found to apply it to the contained select tag)

HTML

<body ng-controller="MainCtrl">

  <form name="validationForm" novalidate>

    <valid-form-group class="form-group" ng-class="{'has-error': validationForm.validInfo.$error.required}" required>

      <select ng-model="data.option" ng-options="option.id as option.message for option in selectOptions" name="validInfo" id="validInfo">
        <option value="">-- Select a Question --</option>
      </select>

    </valid-form-group>

  </form>

</body>

JS

var app = angular.module('plunker', [])
  .controller('MainCtrl', function($scope) {
    $scope.selectOptions = [
      {id: 1, message: 'First option'}, 
      {id: 2, message: 'Second option'}, 
      {id: 3, message: 'Third option'}
    ];
  })
  .directive('validFormGroup', function($compile) {
    return {
      restrict: 'E',
      template: '<div><span ng-transclude></span></div>',
      replace: true,
      transclude: true,
      require: '^form',
      link: function(scope, element, attrs, ctrl) {

        if (attrs.required !== undefined) {

          var selectElement = angular.element(element.find('select'));
          // either of the below produce the same results
          selectElement.attr('ng-required', true);
          //selectElement.attr('required', true);

          // if the below is commented out it wont validate
          // BUT if it is left in it will recompile and add another 3 options
          $compile(selectElement)(scope); 
        }
      }
    };
  });

CSS

.has-error{
  border: solid 1px red;
}

Please note that the sample here is using 'required' (or ng-required) as the added attribute to highlight the fact that Angular does not recognise it unless compiled.

Any help or comments are welcome - a bit disappointed that I can't get this to work, so perhaps there's something fundamental I'm missing...

The plunker should help with visualising my problem.

edit - apologies for the delay in responding to the answers and comments. As mentioned in a comment or two below, personal issues have prevented me from being able to find the time to investigate.

like image 220
Sean Avatar asked Jun 19 '14 21:06

Sean


2 Answers

Try this simple directive:

.directive('validFormGroup', function($compile) {
    return {
        restrict: 'A',
        replace: false,
        require: '^form',
        compile: function (element, attr) {
            if (attr.required !== undefined) {
                var selectElement = element.find('select');
                // either of the below produce the same results
                selectElement.attr('ng-required', true);
                //selectElement.attr('required', true);
            }
        }
    };
});

And use it as html attribute:

<div valid-form-group class="form-group" ng-class="{'has-error': validationForm.validInfo.$error.required}" required>

      <select ng-model="data.option" 
      ng-options="option.id as option.message for option in selectOptions"
      name="validInfo" id="validInfo" >
<option value="">-- Select a Question --</option>
</select>
      <br/>
      <br/>Required invalid? {{validationForm.validInfo.$error.required||false}}
      <br/>
      <br/>

</div>

DEMO

Explanation:

  • I don't use transclude at all in this solution as the purpose of this directive is just to modify the html before compiling with the scope, there is no need for transcluded content which is overly complicated.

  • Here I handle the compile function instead of the link function. compile function is a great place for you to modify html before linking to the scope.

like image 61
Khanh TO Avatar answered Sep 25 '22 13:09

Khanh TO


I can only guess what you are seeing is the result of a double compile during the directives initialization process, that's why you are seeing the double sets of options.

You can solve this by wrapping your compile inside $timeout, which will ensure the compilation happens outside the directive initialization. Here's a working demo and below the directive code:

.directive('validFormGroup', function($compile, $timeout) {
  return {
    restrict: 'E',
    template: '<div><span ng-transclude></span></div>',
    replace: true,
    transclude: true,
    require: '^form',
    link: function(scope, element, attrs, ctrl) {
      if (attrs.required !== undefined) {
        var selectElement = angular.element(element.find('select'));
        $timeout(function(){
          selectElement.attr('ng-required', true);
          $compile(selectElement)(scope);
        });
      }
    }
  };
});      

PS You can achieve similar bootstrap wrapper functionality by using isolated scope on your directive and then checking if your transcluded input/select element has the required attribute set. Define a function on the isolated scope to check for error and tie this function to the form-group ng-class has-error. This way you dont have to use $timeout at all.

like image 27
Beyers Avatar answered Sep 23 '22 13:09

Beyers