I have this scenario where a field is invalid due to another selection on the form. When that selection changes I want to revalidate. I tried calling $setViewValue on the field when the selection changes, but that doesn't refire the validation. Any ideas?
I have a hack working, but I would prefer a clean solution.
I ran into the same issue and found a workaround/feature that appears to be undocumented. If you need to trigger ngModelController to revalidate, you can either do:
ngModelCtrl.$setViewValue(value, 'your event name', true);
or if you don't need to update your model value
ngModelCtrl.$commitViewValue(true);
The true in both cases above is a flag for revalidation. Without this flag, the issue I was running into was that if the model value does not change, then angular simply skips the validation. I am using this way to manually mark a custom control as $dirty
Source:
https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L1843
Write a directive, say validation, and place it on your field in the definition of this directive in the:
link:function(scope,element,args){
element.bind('onfocus',function(){
// Your logic
})
// Similarly, bind other relevant events like key presses, etc.
}
Put a ng-change on your select and broadcast an event in it:
$rootScope.$broadcast("selectChangedEvent");
Then, in a directive you have placed on your field, simply put in the link function:
$rootScope.$on("selectChangedEvent", () => ngModelCtrl.$validate());
$validate runs each of the registered validators of your field.
Related
The fact that ng-change is only for user input, and does not fire when changes are made to the model programmatically, is really causing me a headache today. I'm working with a user input form which has the separate parts of a name, as well as dynamically built and ordered credentials. The form has a "displayName" field that gets updated when the name parts are changed. This is encapsulated in a directive which I need to use in a larger view. Here's where things get hairy. I need to hide the name part fields in my directive and use the outer form's name fields. I thought this would be easy by wiring up a function to update the hidden text input fields, and thus my displayName field. Then I found out programmatic changes to the model do not fire the change event.
I tried creating a watch for one of the name fields to see if I could get it to update the displayName field, but no luck.
this.scope.$watch('provider.firstName', function (event) {
namePropertyChanged2(displayNameOverridden, displayName, provider, credentials);
});
When I change the input field for firstName, it modifies the directive's value 'provider.firstName', and runs the 'namePropertyChanged2' function. The code listed runs in an initialize function, where 'displayName' is a local variable assigned from this.scope.provider.displayName. The watch assignment required me to make local variables instead of passing in controller variables. Not sure why, but whatever. So this function runs and 'displayName' is updated with the correct value... and the input field it's bound to is not updated. Bummer.
What would be ideal is to manually trigger the change event when the model changes, which would update the displayName and much rejoicing to be had.
Assuming a given form such as <form name="myForm">, it's easy enough to watch for validity, error, dirty state, etc. using a simple watch:
$scope.$watch('myForm.$valid', function() {
console.log('form is valid? ', $scope.myForm.$valid);
});
However, there doesn't appear to be an easy way to watch if any given input in this form has changed. Deep watching like so, does not work:
$scope.$watch('myForm', function() {
console.log('an input has changed'); //this will never fire
}, true);
$watchCollection only goes one level deep, which means I would have to create a new watch for every input. Not ideal.
What is an elegant way to watch a form for changes on any input without having to resort to multiple watches, or placing ng-change on each input?
Concerning the possible duplicate and your comment:
The directive solution in that question works, but it's not what I had in mind (i.e. not elegant, since it requires blur in order to work).
It works if you add true as third parameter for your $watch:
$scope.$watch('myFormdata', function() {
console.log('form model has been changed');
}, true);
Further information see the docs.
Working Fiddle (check console log)
Another more angular way would be to use angular's $pristine. This boolean property will be set to false once you manipulate the form model:
Fiddle
Based on my experience with my forms (new dev, but working with Angular for a while now), the elegant way to watch a form for changes is actually not to use any type of watch statement at all actually.
Use the built-in Angular boolean $pristine or $dirty and those values will change automatically on any input field or checkbox.
The catch is: it will not change the value if you add or splice from an array which had me stumped for a while.
The best fix for me was to manually do $scope.MyForm.$setDirty(); whenever I was adding or removing from my different arrays.
Worked like a charm!
I have a form where my intent is for required fields to not always be enforced. For example if the user is saving the document as a draft they can enter as little information as they like, if they try and publish the document then they have to enter all the required fields. I'm using a boolean on the controller which changes according to which button has been pressed e.g.
<input type="text" ng-model="field2" ng-required="enforceRequired" />
The problem is that the fields are not re-evaluated when the boolean changes so the form is submitted and then it becomes invalid. Please see this JSFiddle to see what I mean. If you fill in field1 and then click publish it will succeed on the first click and THEN become invalid.
How can I force the validation to run before the form is submitted?
Yarons is right, you are changing the value too late, by late I mean after the form validations has been run. What you can do as a workaround is, after changing the required value, let angular do another cycle and then show your alert. This can be done via $timeout service, although I must mention that it is usually not a good practise to change the flow of your digest actions. It gets pretty messy pretty soon.
Change your publish function like this (and don't forget to inject $timeout)
$scope.publish = function () {
$scope.enforceRequired = true;
$timeout(function () {
if ($scope.form.$valid) {
alert("Published!");
}
});
};
Working fiddle: http://jsfiddle.net/bh9q00Le/14/
The problem is that you are changing the value of enforceRequired in the middle of the digest loop, so the watchers are not re-rendered before you check the input fields' validity (read about digest here).
If you want to get around it, I suggest one of the following methods:
change the value of enforceRequired before you call saveDraft or publish. see example.
call $scope.$apply() after you change the value of enforceRequired. see another example.
It's a known feature of backbone.js that when you set data that hasn't changed it won't fire the change event, nor will it go through validations. I however need the change event to fire as I'm storing a JSON response from an AJAX call which stores results of backend validation. If the user keeps submitting the form while leaving the same field empty, the backend validation will return the same JSON result and when I save it to the model it won't trigger the change event.
A few things I've tried within the AJAX success callback where I set the data into the model:
Attempted Solution #1
t.model.unset('fieldErrors',{silent: true});
t.model.set({fieldErrors: JSONResponse});
Attempted Solution #2
t.model.set({fieldErrors: null},{silent: true});
t.model.set({fieldErrors: JSONResponse});
Neither of these results in the change event firing a second time when the call is made and the user has the same JSONResponse.
Manually trigger the change event:
t.model.trigger('change', t.model);
or
t.model.trigger('change:fieldErrors', t.model, newFieldErrorsValue);
this.model.set({fieldErrors: JSONResponse}, {silent:true});
this.model.trigger('change:fieldErrors');
see this conversation:
Can I force an update to a model's attribute to register as a change even if it isn't?
I have many events bound to elements in my view, though when I use stickit js to change values in my view by altering the model it doesn't trigger an onChange event.
Is there a way that I can trigger an onchange event for the current model:element after the setting the value in the model without having to write a handler for every binding? This would be for all form elements, input/select/textarea.
I want to avoid the following for each form element on the page:
bindings: {
'#foo': {
observe: 'foo',
afterUpdate: 'forceChange'
},
'#bar': {
observe: 'bar',
afterUpdate: 'forceChange'
},
...
},
forceChange: function(el) { jQuery(el).change() }
One possible hack (with version 0.6.3 only) would be to define a global handler which matches all elements:
Backbone.Stickit.addHandler({
selector: '*',
afterUpdate: function($el) {
$el.trigger('change');
}
});
Since handlers are mixed in with other matching handlers and bindings configurations in the order that they are defined, you couldn't use afterUpdate in any of your bindings without overwriting this global, all-matching handler since the bindings configurations are the last to be mixed in. You can read more about it here.
Ahhh, that comment clarifies matters. So, in Javascript when you change an input's value "manually" (whether through jQuery or through someElement.value =) the browser won't, as you noticed, fire a change event. Change events (and most other events for that matter) are only fired in response to user actions, not to Javascript.
Luckily, just as you can "manually" change a value, you can also "manually" trigger an event. In jQuery the syntax for that is:
$(yourElement).trigger('change');
If you need to control things like e.target you can read up on the jQuery trigger documentation for the details, but that's the basic idea.
You can even chain the value-changing and event-triggering together if you want:
$(yourElement).val('newValue').trigger('change');