1

I have a Angular form where i am doing validation. And i want the form to be not submit-able if any required fields are empty and validation fails. The form give bellow.

<form name="userForm" novalidate>
    <fieldset ng-disabled="data.externalUserDisable">
        <div class="restrictwidth">
            <h1 ng-if="data.urlId === 'new'" translate>NewUser</h1>
            <div class="form-group required" ng-class="{ 'has-error' : userForm.username.$invalid && !userForm.username.$pristine }">
                <label class="control-label" translate>UsernameEmail</label>
                <input class="form-control" type="email" id="username" name="username" ng-model="user.Username" ng-model-options="{'update On': 'blur'}" ng-disabled="(!data._editMode && !(user.Status === 'NotVerified' || user.Status === 'ActivationPending'))" ng-maxlength="256" required>
                <div class="error-messages" ng-messages="userForm.username.$error" ng-if="userForm.$submitted || !userForm.username.$pristine">
                    <div ng-message="required" class="help-block error-message"><span translate="UsernameRequired"></span></div>
                </div>
            </div>
            <div class="row">
                <div class="form-group required col-sm-6" ng-class="{ 'has-error' : userForm.firstName.$invalid && !userForm.firstName.$pristine }">
                    <label class="control-label" translate>FirstName</label>
                    <input class="form-control" type="text" id="firstName" name="firstName" ng-model="user.FirstName" ng-maxlength="64" required>
                    <div class="error-messages" ng-messages="userForm.firstName.$error" ng-if="userForm.$submitted || !userForm.firstName.$pristine">
                        <div ng-message="maxlength" class="help-block error-message"><span translate="FirstNameLength"></span></div>
                        <div ng-message="required" class="help-block error-message"><span translate="FirstNameRequired"></span></div>
                    </div>
                </div>
                <div class="form-group required col-sm-6" ng-class="{ 'has-error' : userForm.lastName.$invalid && !userForm.lastName.$pristine }">
                    <label class="control-label" translate>LastName</label>
                    <input class="form-control" type="text" id="lastName" name="lastName" ng-model="user.LastName" ng-maxlength="64" required>
                    <div class="error-messages" ng-messages="userForm.lastName.$error" ng-if="userForm.$submitted || !userForm.lastName.$pristine">
                        <div ng-message="maxlength" class="help-block error-message"><span translate="LastNameLength"></span></div>
                        <div ng-message="required" class="help-block error-message"><span translate="LastNameRequired"></span></div>
                    </div>
                </div>
            </div>               
            <div class="form-group required" ng-class="{ 'has-error' : userForm.masterEngagement.$invalid && !userForm.masterEngagement.$pristine }">
                <label class="control-label" translate>MasterEngagement</label><i ng-show="loadingLocations" class="glyphicon glyphicon-refresh"></i>
                <input type="text" name="masterEngagement" ng-model="user.MasterEngagementId" ng-if="!data.externalUserDisable" typeahead="engagement.EngagementId as engagement.EngagementName for engagement in getLocation($viewValue)" typeahead-on-select='onSelect($item, $model, $label)'
                       typeahead-loading="loadingLocations" typeahead-editable="false" typeahead-input-formatter="formatLabel($model)" typeahead-template-url="customTemplate.html" typeahead-wait-ms="500" class="form-control" ng-disabled="singleEngagementAdmin !== '0'" required>
                <input type="text" name="masterEngagement" ng-model="user.MasterEngagementName" ng-if="data.externalUserDisable" class="form-control" ng-disabled="singleEngagementAdmin !== '0'" required>
            </div>

            ...

            <div class="restrictwidth">
                <button ng-if="data.urlId === 'new'" type="submit" class="btn btn-primary btn-lg pull-right ladda-button" data-style="expand-left" ng-save-in-progress ng-disabled="{{ userForm.$invalid }}" ng-click="insert()" translate>Save</button>
                <button ng-if="data.urlId !== 'new'" type="submit" class="btn btn-primary btn-lg pull-right ladda-button" data-style="expand-left" ng-save-in-progress ng-disabled="userForm.$invalid || (!superUser && user.Superuser)" ng-click="update()"><span class="ladda-label" translate>Update</span></button>                 
                <a class="btn btn-link pull-left" ng-click="back()" type="button"><span class="ladda-label" translate>Cancel</span></a>
            </div>
        </div>
    </fieldset>
</form>

The use of ng-disabled to disable the save button is correct. But the button is not disabled. The button is not disabled when the form loads and even for the validation errors the button is not disabled.

I have checked the userForm object and the $invalid value is true and $valid valus is false.

What i would like to know is what are the reasons for this to happen.? What could be the possible causes?

Update

The output form printing the userForm..

{
  "$error": {
    "required": [
      {
        "$validators": {},
        "$asyncValidators": {},
        "$parsers": [],
        "$formatters": [
          null
        ],
        "$viewChangeListeners": [],
        "$untouched": true,
        "$touched": false,
        "$pristine": true,
        "$dirty": false,
        "$valid": false,
        "$invalid": true,
        "$error": {
          "required": true
        },
        "$name": "username",
        "$options": {
          "update On": "blur",
          "updateOnDefault": true
        }
      },
      {
        "$validators": {},
        "$asyncValidators": {},
        "$parsers": [],
        "$formatters": [
          null
        ],
        "$viewChangeListeners": [],
        "$untouched": true,
        "$touched": false,
        "$pristine": true,
        "$dirty": false,
        "$valid": false,
        "$invalid": true,
        "$error": {
          "required": true
        },
        "$name": "firstName",
        "$options": null
      },
      {
        "$validators": {},
        "$asyncValidators": {},
        "$parsers": [],
        "$formatters": [
          null
        ],
        "$viewChangeListeners": [],
        "$untouched": true,
        "$touched": false,
        "$pristine": true,
        "$dirty": false,
        "$valid": false,
        "$invalid": true,
        "$error": {
          "required": true
        },
        "$name": "lastName",
        "$options": null
      },
      {
        "$validators": {},
        "$asyncValidators": {},
        "$parsers": [
          null
        ],
        "$formatters": [
          null,
          null
        ],
        "$viewChangeListeners": [],
        "$untouched": true,
        "$touched": false,
        "$pristine": true,
        "$dirty": false,
        "$valid": false,
        "$invalid": true,
        "$error": {
          "required": true
        },
        "$name": "masterEngagement",
        "$options": null
      }
    ]
  },
  "$name": "userForm",
  "$dirty": false,
  "$pristine": true,
  "$valid": false,
  "$invalid": true,
  "$submitted": false,
  "username": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": false,
    "$invalid": true,
    "$error": {
      "required": true
    },
    "$name": "username",
    "$options": {
      "update On": "blur",
      "updateOnDefault": true
    }
  },
  "firstName": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": false,
    "$invalid": true,
    "$error": {
      "required": true
    },
    "$name": "firstName",
    "$options": null
  },
  "lastName": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": false,
    "$invalid": true,
    "$error": {
      "required": true
    },
    "$name": "lastName",
    "$options": null
  },
  "title": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": true,
    "$invalid": false,
    "$error": {},
    "$name": "title",
    "$options": null
  },
  "employeeNumber": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": true,
    "$invalid": false,
    "$error": {},
    "$name": "employeeNumber",
    "$options": null
  },
  "userInfo": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": true,
    "$invalid": false,
    "$error": {},
    "$name": "userInfo",
    "$options": null
  },
  "timeZone": {
    "$viewValue": "W. Europe Standard Time",
    "$modelValue": "W. Europe Standard Time",
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": true,
    "$invalid": false,
    "$error": {},
    "$name": "timeZone",
    "$options": null
  },
  "culture": {
    "$viewValue": "en-US",
    "$modelValue": "en-US",
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": true,
    "$invalid": false,
    "$error": {},
    "$name": "culture",
    "$options": null
  },
  "policy": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [],
    "$formatters": [],
    "$viewChangeListeners": [
      null
    ],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": true,
    "$invalid": false,
    "$error": {},
    "$name": "policy",
    "$options": null
  },
  "masterEngagement": {
    "$validators": {},
    "$asyncValidators": {},
    "$parsers": [
      null
    ],
    "$formatters": [
      null,
      null
    ],
    "$viewChangeListeners": [],
    "$untouched": true,
    "$touched": false,
    "$pristine": true,
    "$dirty": false,
    "$valid": false,
    "$invalid": true,
    "$error": {
      "required": true
    },
    "$name": "masterEngagement",
    "$options": null
  }
}
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Kasun Kodagoda
  • 3,956
  • 5
  • 31
  • 54
  • The only reason I could guess is missing `model`! Need to see the `controller` or running code for better understanding of the problem! – Rayon Oct 05 '15 at 12:06
  • Create a demo that replicates the problem. What does `ng-save-in-progress` do? Bad practice naming custom directives with `ng` prefix which usually denotes a core functionality – charlietfl Oct 05 '15 at 12:31
  • can you provide plunker for same? – Paresh Gami Oct 05 '15 at 13:10
  • @charlietfl The ng-save-in-progress is a directive used for showing a spinner icon while the save is happening. – Kasun Kodagoda Oct 06 '15 at 05:04
  • Was more interested in if it was affecting `ng-disabled`. FYI - am curious about your country...I am from US but spend 8 months living in Negombo in mid 90's while all the civil war was going strong. Hopefully that is all over and things are more prosperous – charlietfl Oct 06 '15 at 05:10
  • @charlietfl Really? :) Yeah, the war is over now. Everything is peaceful now.. You should surely visit again. Lot;s of places to see :) I'll try removing the `ng-save-in-progress` – Kasun Kodagoda Oct 06 '15 at 05:30
  • Made me think of the hotel I lived in... was one of the best on beach there. I was just looking at pictures and is far far nicer than when I was there. We used to watch the water canon truck all the time come through to stop uprisings in the area...lol. Yes indeed ...sir lanka is an amazing place to visit. I also lived right next to village of fishermen... was always a shame when one of them didn't come home in the morning – charlietfl Oct 06 '15 at 05:33
  • @charlietfl Since the war is over now, you can visit places all over the country. You should definitely come.. :) Regarding the question, removing `ng-save-in-progress` did not work. What are other possibilities that could be affecting ng-disabled? – Kasun Kodagoda Oct 06 '15 at 05:40
  • Not sure about ng-disabled. One trick i use somethimes is print the form object to the view to see what properties are doing `
    {{userForm | json}}
    `. A bit easier than in dev tools sometimes
    – charlietfl Oct 06 '15 at 05:43
  • also notice that fieldset has ng-disabled...not sure if that has anything to do with it – charlietfl Oct 06 '15 at 05:45

1 Answers1

0

Actually everything works fine for button tag, check this demo: http://plnkr.co/edit/fJoq7RLrDNYMxYwh6bPq?p=preview

The issue in your case is the ng-if directive which creates an inner scope, but form is declared in the parent scope. And in this case, to disable the button based on the form you have, you need to access it from parent. Simplified it would be like this:

<button type="submit" ng-disabled="$parent.userForm.$invalid">Save</button>

Edit: Having two ng-if means you have to go up with two parent scopes in this case, in the original code, having one ng-if access to invalid property is via $parent.userForm.$invalid, with two ng-if is

<button type="submit" ng-if="data.urlId === 'new'" ng-disabled="$parent.$parent.userForm.$invalid">Save</button>

This can be avoided by using ng-show or ng-hide instead of ng-if, the difference is that the first two don't create inner scopes, while each ng-if will create its scope inside its parent. If you change both ng-if into ng-show, then the button will work with your original ng-disabled="userForm.$invalid".

Diana R
  • 1,174
  • 1
  • 9
  • 23
  • Adding $parent did not work. There is an ng-if in the button itself. I updated the code. The value userForm.$invalid is true when the form loads. and when the required fields are filled then it becomes false. But the ng-disabled does not work for some reason. – Kasun Kodagoda Oct 06 '15 at 05:07
  • Does the button have to be inside the element that the `ng-if`s are added for the child scope count to go up. Something like this `div > div > div > button` If all 3 `div`s have `ng-if` then the count would be 3. Or even if the button element is not inside those divs, would the count still be 3? – Kasun Kodagoda Oct 06 '15 at 07:34
  • Don't count divs or any other html tags, count only `ng-if`s, but indeed if you have `
    {{$parent.$parent.$parent.$parent.form}}
    `, you will have to go up 4 parrent scopes to access the outer form.
    – Diana R Oct 06 '15 at 07:49