angular-bootstrap-show-errors
angular-bootstrap-show-errors copied to clipboard
Does not work with ngSwitch inside ngRepeat
I tried using the fix from #16, but unfortunately it doesn't work for me. What I have is something that looks like:
<div ng-repeat="field in fields" class="row form-group" show-errors>
<div class="col-md-8" ng-switch on="field.type">
<select ng-switch-when="select" name="form{{$index}}" class="form-control"></select>
<input type="number" ng-switch-when="number" name="form{{$index}}" class="form-control">
<input type="text" ng-switch-when="text" name="form{{$index}}" class="form-control">
</div>
</div>
This results in the error: show-errors element has no child input elements with a 'name' attribute and a 'form-control' class
However, if I change the code to:
<div ng-repeat="field in fields" class="row form-group" show-errors>
<div class="col-md-8">
<input type="text" name="form{{$index}}" class="form-control">
</div>
</div>
Everything works as expected.
I had a similar problem (with ngMessages
) and wrote a simpler version of the directive for my own needs: http://plnkr.co/edit/LzlcxTXwZNogwCznPdIE?p=preview
It should handle ngSwitch and similar without problem.
Thanks @VanTanev, your version seems to be working in our case as well!
I'm having the same issues. I will take a look at @VanTanev version, thanks
@gommo I just went back to my code and updated it a little. In a real project, it turns out that you often times need to put multiple .form-control
input elements inside a single div.
This update makes the directive detect errors on multiple elements: http://plnkr.co/edit/a6XWR657EpTOW2JL7bhM?p=preview
And now, for some extra. In my projects, I do backend validation on all forms. Sometimes, there are validations that only the backend can do. In this case, I need to mark a .form-group
as having an error based on the errors returned from the backend. In my case, those have the form of:
var user = {
"username": "VanTanev",
"password": "123",
"errors": {
"username": ["username is already in use"]
}
}
So, in order to make the showErrors
directive show errors for this as well, I made the following changes:
<!-- the important part --> <div show-errors="user"> <!-- the important part -->
<input type="text" name="username" class="form-control" ng-model="user.username" />
<input type="text" name="password" class="form-control" ng-model="user.password" />
</div>
and modified the watch functuon in the directive:
$scope.$watch(function() {
// on scope changes, check if the input's validation status has changed.
// additionally, if an input has not been touched or the form has not been
// yet submitted, consider the element valid
var hasFormError = ngFormCtrl[childInputName].$invalid
&& (ngFormCtrl[childInputName].$touched || ngFormCtrl[childInputName].$dirty || ngFormCtrl.$submitted);
// additionally check the model for errors on this field
var hasModelError = !!($attrs['showErrors'] && $scope.$eval($attrs['showErrors'] + '.errors["' + childInputName + '"].length'));
return hasFormError || hasModelError;
}, function(is_invalid) {
$element.toggleClass('has-error', is_invalid);
});
Basically, I give the model object's name to the showErrors
directive by setting it as an attribute on the div, and can then access that name with $attrs['showErrors']
. Afterwards, I check for model errors by evaluating !!user.errors["username"].length
- if this is true, the user object has validation errors from the backend and the field must be marked with .has-error
class.
Great work ! @VanTanev Do you wanna put it on your github ?
So basically the real magic here is the timeout right? I came here because I had a similar issue where the show-errors didnt see an input generated several levels of components/directives deep down my DOM nest.
@VictorioBerra For the most part, yes. This is the current version that I use in my projects: https://gist.github.com/VanTanev/724cef9f2950cb2b246e33bd407187e0
import _ from 'lodash'
/* @ngInject */
export default function highlightErrorsDirective($timeout) {
var directive = {
restrict: 'A',
require: '^form',
link: highlightErrorsLinkFn,
};
return directive;
////////////////////////
function highlightErrorsLinkFn(scope, element, attr, ngFormCtrl) {
// This timeout is necessary to handle the case when we have
// ng generated code inside of the highlight-errors element.
// We want to allow interpolation of child elements, so we just
// offset the attachment of the watchers until the browser renders
// the next frame, by using a $timeout(func, 0, false)
let promise = $timeout(function() {
// we search for .form-control where the name attriubte is set
var childInputs = element[0].querySelectorAll('.form-control[name]');
// and add watchers for all elements
_.each(childInputs, function(childInput) {
var childInputName = childInput && childInput.getAttribute('name');
if (!childInputName || angular.isUndefined(ngFormCtrl[childInputName])) {
// if we cannot find the expected element, throw an error and
// halt compilation
throw new Error(`highlightErrors directive requires that you have
a child ".form-control" element with a "name" attribute.`);
}
scope.$watch(function() {
// on scope changes, check if the input's validation status has changed.
// additionally, if an input has not been touched or the form has not been
// yet submitted, consider the element valid
var hasFormError = ngFormCtrl[childInputName].$invalid &&
(ngFormCtrl[childInputName].$touched ||
ngFormCtrl[childInputName].$dirty ||
ngFormCtrl.$submitted);
var hasModelError = false;
if (attr.highlightErrors) {
hasModelError = !!scope.$eval(
`${attr.highlightErrors}.errors["${childInputName}"].length`
);
}
return hasFormError || hasModelError;
}, function(hasError) {
element.toggleClass('has-error', hasError);
});
});
}, 0, false);
scope.$on('$destroy', () => $timeout.cancel(promise))
}
}
All the best :)
@VanTanev I hope im not asking too much, but could you create a repo for this and add a license? maybe MIT? That way we can all contribute and use this in projects at work, etc.
I will add the MIT license to the gist, and see about making a dedicated repo; Would need to setup tests, builds, docs, etc etc etc for that.