When a user opens a page that has a bunch of required fields, all they see is a bunch of red. Not very inviting. I would much rather that validation is hidden until $touched === true. I would also like to have the ability to control when validation is shown.
I appreciate how helpful angular-material is, but I really like that bootstrap allows me to specify when to show validation errors by requiring me to add the has-error class myself. This gives me a lot of flexibility.
I haven't looked at the implementation for angular-material, but perhaps I could specify a function which would determine whether validation should be visible for a specific field?
However it's done, I just don't want to invite users to my page with a bunch of invalid fields. Let them touch the fields first, then we'll tell them it's invalid if it still is after they've had a chance to do something with it.
Sorry, just realized that it doesn't greet the user with a bunch of red. However, it goes red as soon as focus comes into the input. I would rather wait until $touched is set to true which means the user has blurred the input.
There is a way to specify when to show an error: By using the md-is-error attribute.
However there are some issues/bugs with properly detecting the $touched state (there are several issues about this, I believe).
I hadn't ever heard of issues with the $touched state. I switched my demo to bootstrap and everything is working really well: http://jsbin.com/zuguxi/edit
Sorry, just realized I never gave the original example here: http://jsbin.com/cadeko/edit
This turned out to be longer than anticipated, so long story short:
Due to some bugs in the current implementation:
mdInputContainer gets set to touched upon focus.This is a bug with setting the touched state of the input. It can be found in input.js#L183-192:
element
.on('focus', function(ev) {
containerCtrl.setFocused(true);
// Error text should not appear before user interaction with the field.
// So we need to check on focus also
ngModelCtrl.$setTouched();
if ( isErrorGetter() ) containerCtrl.setInvalid(true);
})
Here is what is going on:
mdIsError attribute which can be used to determine when the error should be "applied"/shown.$parsed and (incorrectly) used for determining if the error should be applied using the isErrorGetter() function.ngModelCtrl.$invalid && ngModelCtrl.$touched. <-- (this is the case in your example)isErrorGetter() is $watched and setInvalid() is called on the mdInputContainer's controller according to its return value.ngModelCtrl.$setTouched();if ( isErrorGetter() ) containerCtrl.setInvalid(true);As a result of (5):
touched (although this is not consistent with the default semantics of $touched).isErrorGetter() (which checks the controller's internal state) evaluates to true, which results in calling setInvalid(true) on the mdInputContainer's controller.mdInputContainer receives the md-input-invalid class, thus stying the containing input with the warn-500 color (by default red).$digest cycle and there is not explicit $apply, the new state is not properly propagated, thus:ng-touched class.ngShow on the messages' div is not re-evaluated (it would evaluate to true and show the errors, if a digest cycle was properly triggered). Both of the above can be "fixed" by triggering a digest "manually" from the console (for illustration pusposes only).
(Needless to say, that the Bootstrap version (which does not rely on mdInputContainer) works as expected as it avoids the aforementioned bugs.)
Thanks for the explanation @gkalpak. Totally makes sense where this came from. Obviously triggering the digest manually is not an option. In angular-formly we have logic like this:
// scope.fc = the ngModelController
// scope.options = something that the directive user can manipulate
scope.$watch(function() {
if (typeof scope.options.validation.show === 'boolean') {
return scope.fc.$invalid && scope.options.validation.show;
} else {
return scope.fc.$invalid && scope.fc.$touched;
}
}, function(show) {
options.validation.errorExistsAndShouldBeVisible = show;
scope.showError = show; // <-- just a shortcut for the longer version for use in templates
});
This is really handy because in templates, people can do something like this (contrived bootstrap example):
<div class="form-group" ng-class="{'has-error': showError}"> <!-- <-- notice that -->
<label for="my-input" class="control-label">My Label</label>
<input ng-model="my.model" name="myForm" class="form-control" id="my-input" />
<div ng-messages="fc.$error" ng-if="showError"> <!-- <-- and that -->
<!-- messages here -->
</div>
</div>
I think the md-is-error works great because it gives the same kind of control that options.validation.show gives, where you can show errors even if the user hasn't touched an input which is useful sometimes. But the more common case is after the user has interacted with the field.
If it were me, I would do what we're doing in angular-formly. Don't explicitly set the ngModelController to invalid and $touched on focus, but instead only show error state if it's $invalid && ($touched || isErrorGetter()).
@gkalpak regarding issue 5, I'm guessing the reason its thrown into an error state on focus is because, in the docs for example, all the fields have required directive. So you focus the input, it checks to see if its required, but you havent started typing so no value is present yet, throwing into immediate error state.
@jasonayre: The inputs are invalid from the beginning (because they are required and empty). The problem is when they show their invalidity (which in the intended default case should happen after the first blur, but happens on focus).
Fixed with SHA 747eb9c
Thanks @ThomasBurleson :D
Hi, maybe I did something really wrong but it does not work with 0.8.3
this is my mini example: http://codepen.io/mxab/pen/JoewqY?editors=101
The myForm.myValue.$touched seems to work correctly at the ng-messages block,
but still if I simply focus the input it turns red
Yes, looks like it doesn't work in 0.8.3.
+1
Most helpful comment
Thanks for the explanation @gkalpak. Totally makes sense where this came from. Obviously triggering the digest manually is not an option. In
angular-formlywe have logic like this:This is really handy because in templates, people can do something like this (contrived bootstrap example):
I think the
md-is-errorworks great because it gives the same kind of control thatoptions.validation.showgives, where you can show errors even if the user hasn't touched an input which is useful sometimes. But the more common case is after the user has interacted with the field.If it were me, I would do what we're doing in
angular-formly. Don't explicitly set thengModelControllerto invalid and$touchedon focus, but instead only show error state if it's$invalid && ($touched || isErrorGetter()).