8

JSFiddle here: http://jsfiddle.net/c6tzj6Lf/4/

I am dynamically creating forms and buttons and want to disable the buttons if the required form inputs are not completed.

HTML:

<div ng-app="choicesApp">
  <ng-form name="choicesForm" ng-controller="ChoicesCtrl">
    <div ng-bind-html="trustCustom()"></div>
    <button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
      {{button.text}}
    </button>
  </ng-form> 
</div> 

JavaScript:

angular.module('choicesApp', ['ngSanitize'])
  .controller('ChoicesCtrl', ['$scope', '$sce', function($scope, $sce) {
    $scope.custom = "Required Input: <input required type='text'>";
    $scope.trustCustom = function() {
      return $sce.trustAsHtml($scope.custom);
    };
    $scope.buttons = [
      {text:'Submit 1'},
      {text:'Submit 2'}];
}]);

choicesForm.$invalid is false and does not change when entering text into the input field.

Solution:

I ended up using the angular-bind-html-compile directive from here: https://github.com/incuna/angular-bind-html-compile

Here is the relevant bit of working code:

<ng-form name="choicesForm">
  <div ng-if="choices" bind-html-compile="choices"></div>
  <button ng-click="submitForm()" ng-disabled="choicesForm.$invalid">
    Submit 
  </button>
</ng-form>

And choices might be a snippit of HTML like this:

<div><strong>What is your sex?</strong></div>
<div>
  <input type="radio" name="gender" ng-model="gender" value="female" required>
  <label for="female"> Female</label><br>
  <input type="radio" name="gender" ng-model="gender" value="male" required>
  <label for="male"> Male</label>
</div>
disperse
  • 1,216
  • 10
  • 22

5 Answers5

6

The main problem is that ngBindHtml doesn't compile the html - it inserts the html as it is. You can even inspect the dynamic input and see that it doesn't have the ngModel's CSS classes (ng-pristine, ng-untouched, etc) which is a major red flag.

In your case, the form simply doesn't know that you've added another input or anything has changed for that matter. Its state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. These controllers are added automatically when an ngModel is linked.

  • For example this <input required type='text'> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
  • But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.

The ngDisabled directive on your buttons works as expected since it depends on the form's $invalid property.

See this fiddle which showcases how ngModel registers its controller. Note that the html containing the dynamic input gets compiled after 750ms just to show how NgModelControllers can be added after FormController has been instantiated.

There are a few solutions in your case:

  • use a custom directive to bind and compile html - like this one

  • use ngInclude which does compile the html

  • use $compile to compile the newly added HTML but this is a bit tricky as you won't know exactly when to perform this action

Community
  • 1
  • 1
Cosmin Ababei
  • 7,003
  • 2
  • 20
  • 34
2

This is an answer yet imcomplete because i cannot do the code at the moment.

I think your html will be included, not compiled. So the inputs are not bind to angular and are not part of the angular form object.

The only way i see is to use a directive that will compile the passed html and add it to your form. This may be quite tricky though, if you want to go on this way i suggest to edit your question to ask for the said directive.

However i'm not really familiar with $compile so i don't know if it'll work to just add $compile around $sce.trustAsHtml()

Walfrat
  • 5,363
  • 1
  • 16
  • 35
1

You can write a method as ng-disabled does not work with booleans, it works with 'checked' string instead:

So on your controller place a method :

$scope.buttonDisabled = function(invalid){
        return invalid ? "checked" : "";
    };

And on your view use it on angular expression :

<button ng-repeat="button in buttons" ng-disabled="buttonDisabled(choicesForm.$invalid)">

Here is a working fiddle

Rosmarine Popcorn
  • 10,761
  • 11
  • 59
  • 89
  • I'm not sure that's the issue. Notice that putting text in the "Static Input" text box will enable the buttons but putting text in the "Static Input" text box does not. – disperse Mar 21 '16 at 19:48
1

Working DEMO

This is the solution you are looking for. You need a custom directive. In my example I have used a directive named compile-template and incorporated it in div element.

<div ng-bind-html="trustCustom()" compile-template></div>

Directive Code:

.directive('compileTemplate', function($compile, $parse){
    return {
        link: function(scope, element, attr){
            var parsed = $parse(attr.ngBindHtml);
            function getStringValue() { return (parsed(scope) || '').toString(); }

            //Recompile if the template changes
            scope.$watch(getStringValue, function() {
                $compile(element, null, -9999)(scope);  //The -9999 makes it skip directives so that we do not recompile ourselves
            });
        }         
    }
});

I found the directive in this fiddle.

Khalid Hussain
  • 1,675
  • 17
  • 25
  • Hmm, doesn't seem to be working. Try entering text into the Dynamic Input text box, the buttons are not enabled. – disperse Mar 23 '16 at 15:04
  • As you have used `ng-disabled="choicesForm.$invalid"` condition, button will only be enable if total form is valid. Both dynamic and static input fields are required. If you enter value only in one input field, still another field is required, that means form is invalid. If you want to enable button after entering input in any of two fields, your code should be something like this `ng-disabled="choicesForm.staticField.$invalid or choicesForm.dynamicField.$invalid"` – Khalid Hussain Mar 23 '16 at 15:19
  • What do you actually want? do you want to enable button when total form is valid? or when any of two input fields is valid? – Khalid Hussain Mar 23 '16 at 15:26
  • Ahh, yes, you're correct. I didn't enter any text into the Static Field text input. – disperse Mar 23 '16 at 17:44
  • Does it solve your problem? If it solve your problem, please accept it as answer. Thank you..... – Khalid Hussain Mar 24 '16 at 01:00
1

I believe what is really happening though due to jsfiddle I'm unable to dissect the actual scopes being created here.

<div ng-app="choicesApp">
  <ng-form name="choicesForm" ng-controller="ChoicesCtrl">
    <div ng-bind-html="trustCustom()"></div>
    <button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
      {{button.text}}
    </button>
  </ng-form> 
</div> 

The first div is your top level scope, your form is the first child scope. Adding the div using a function creates the dynamically added input field as a child of the first child, a grandchild of the top level scope. Therefore your form is not aware of the elements you're adding dynamically causing only the static field to be required for valid form entry.

A better solution would be to use ng-inclue for additional form fields or if your form isn't to large then simply put them on the page or template you're using.

aallord
  • 357
  • 1
  • 12