3

I am fairly new to Angular and trying to make a directive that will construct a form input, usually a text-input, but sometimes a select box based on whether or not the input is associated with an array of options. Simplifying down, my code looks roughly like this:

html

<init ng-init = "ops = [
  {value:'hello',label:'Hello All'},
  {value:'bye',label:'Good-bye everyone'}]"></init>
<init ng-init = "fType = 
  {id:'greeting',label:'Greeting',type:'enum', 'options':ops}">    
</init>

<simpleselect field="fType" ng-Model="foomodel"></simpleselect>

{{foomodel}}

Directive

.directive('simpleselect',function(){
  return {
    restrict: 'E',
    replace:true,
    template:[
      '<div><select ',
        'ng-if ="type=\'select\'"', 
        'name="{{field.id}}"',
        'ng-model="ngModel" ',
        'ng-options="option.value as option.label for option in field.options">',
      '</select>{{ngModel}}</div>',
    ].join(),
    scope:{
      field:'=',
      ngModel:'='
    },
    link:function(scope, elem, attrs, ctrl){
      scope.type = 'select';
    }
  }
});

This almost works. If I remove the ng-if on the select box, my select box and my model stay in sync just fine. But what I want is to be able to choose which control within the directive. Is this a misuse of ng-if and is there another path?

Gus Reiber
  • 41
  • 1
  • 9
  • Might be because ngIf does not render the enclosed HTML at all if false. ngShow might be worth a try as an alternative. – imcg Dec 24 '13 at 19:34
  • 1
    [http://docs.angularjs.org/api/ng.directive:ngIf] **Partial answer to my question** Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope. – Gus Reiber Dec 24 '13 at 20:24

4 Answers4

0

Can use template:function(element,attrs) if using angular version >=1.1.4

template:function(element,attrs){
    var template='<div>';
    var type= attrs.fieldType;
    if( type=='select'){
        template+='<select ng-options=......>';
    }
    if(  type=='text' ){
        template +='<input ......./>';
    }
    template +='</div>';
    return template;

}
charlietfl
  • 170,828
  • 13
  • 121
  • 150
0

Modify your template as follows:

template: [
    '<div ng-if="field.type==\'select\'">', // <-- move ng-if here
        '<select name="{{field.id}}"',
                'ng-model="ngModel" ',
                'ng-options="option.value as option.label for option in field.options">',
        '</select>',
        '{{ngModel}}',
    '</div>'
].join(''),

Also note there are couple of errors:

1). ng-if should have == instead of = and field.type instead of just type

2). .join('') instead of .join()

Demo http://jsfiddle.net/2YE3b/

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • Thx for setting up the JS fiddle. Your answer is what I was hoping for, but it looks like the 2 way bind with the scope still breaks. If you dump {{foomodel}} in the HTML, you will see that it is not updated, sadly. – Gus Reiber Dec 24 '13 at 20:23
  • Because you use `ng-if`. If you want 2way binding you should use `ng-show` instead. – dfsq Dec 24 '13 at 20:57
0

As a couple of folks suggested, I could have used ng-show, but I didn't want to pollute my DOM with all the input types I was not using. I also could have set my directive with a list of individual properties instead of passing in a 'field' object and then watching them in my template function to determine the particulars of my input, like charlietfl's solution.

Instead, since I want to determine which input type control to use based on a number of attributes in the model itself, I have chosen to resolve a good portion of the rendering of my control in the link method of my directive, using the $compile service. Then I can both make macro layout decisions based on the model I pass into scope and still resolve the particulars of each input using angular style template syntax.

For a simple selectbox, this would have been overkill and either of the two other answers here would have been better, but because I want my directive to determine if a control should be a text input, textarea, selectbox, radio buttons, or checkboxes depending only on the requirements of the model I need to be able to read the model first and then compile with it.

Doing rendering in the link method feels a bit wrong, so I don't mean to be saying I have a great solution, but if it helps anyone, that's great. If others with more experience with Angular than me find that offensive, I would also love to be straightened out. :^)

Here is an example of my more complicated checkbox option within the directive:

    link:function(scope, elem, attrs, ctrl){
      ...some logic to examine the model to determine which input type to use...

    if(scope.type === 'checkbox'){
        if(typeof scope.ngModel === 'string') scope.ngModel = scope.ngModel.split(/[ ,]+/);
        tmp = [
        '<div class="option chk tall" ng-repeat="option in field.options">',
          '<label><input ng-model="ngModel" ng-value="option.value" ng-checked="ngModel.indexOf(option.value) > -1" name="{{field.id}}" type="checkbox" />{{option.label}}</label>',
          '<div class="description">{{option.description}}</div>',
        '</div>{{ngModel}}'].join('');
        elem.on('change',function(e){
          if(e.target.checked && scope.ngModel.indexOf(e.target.value) < 0) scope.ngModel.push(e.target.value);              
          if(!e.target.checked)
            scope.ngModel.splice(scope.ngModel.indexOf(e.target.value),1);
        });
      }

      elem.find('div').html(tmp);
      $compile(elem.contents())(scope);
    }

I am not at all in love with the on-click stuff to keep my model and UI in sync, but for now, I am going to live with it.

Gus Reiber
  • 41
  • 1
  • 9
0

I had a similar problem and you can actually access the parent model via $parent.boundattribute.

As described somewhere in the comments, ng-if adds a subscope and thus the model does not update backwards.

In my case ng-show would not work, I had to really remove the part of the DOM and this solved the problem.

<select ng-if="type='select'"
    name="{{field.id}}"
    ng-model="$parent.ngModel"
    ng-options="option.value as option.label for option in field.options">
</select>
lisa p.
  • 2,138
  • 21
  • 36