Using a variable for ng-required doesn't re-evaluate fields - angularjs

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.

Related

Angular, wait until the digest cycle completes before returning from function?

I would like to highlight a field in red if the user tries to submit the form while the field is empty. So I set a showErrors boolean to true on submit, and conditionally require my field based on this value ng-required='showErrors'.
I define a isValid function which triggers a digest cycle to allow the showErrors value to propagate, but the function is not behaving as I would like. With the field empty, the first time the submit button is clicked isValid returns true and then false on subsequent clicks, but I would like it to always return false if the field is empty.
How can I force my isValid function to wait for the digest cycle to propagate prior to returning validity?
https://plnkr.co/edit/wH6HfawquHJwR69AUPRI
From personal experience it would be easier to control the hightlight by using css on the form level.
ng-required is reserved for use when we need to set required dynamically, for example user can choose to input phone or email, then required for email input field is only needed when user select email.
<form ng-class='{showErrors: showErrors}'>
<input required />
</form>
.showErrors .ng-invalid {
border-color: red;
}
TlDr; use $timeout(function(){...});
The answer provided by #Icycool does do the trick in this use case. But the reason i came to this post was the reason of the title of the post: "How to wait till the $digest cycle is done?"
I did some searching and found this post on coding-geek explaining the different elements that come to hand in the $digest cycle.
It explains that $timeout can be used to wait a cycle, (or more, if you add the timeout parameter) It also says that you can use $evalAsync but this does not always do the trick for me.
So here is an example where a scope variable is watched and a input element is triggered. It's kind of abstract and psuedo because I do not have a good use case right now.
$scope.$watch('vm.foo', function(newFoo) {
if (newFoo) {
$timeout(function() {
$window.alert('new Foo set!' + newFoo);
});
} else {
$timeout(function() {
$window.alert('Foo was removed!');
angular.element('input').focus();
}, 2000); // waits for 2 secs before alert and focus
}
});
I've found this plunker that uses plain timeout's and $apply, but you can fiddle around with it to make it work for your use case, i found it helping to understand cycles: https://plnkr.co/edit/1nzydOS7AVONmXh9?preview

Watch form model for changes

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!

$watch fires on focus lost

I have a textarea tag with jquery.nicescroll pluggin and ng-model attached to it.
<textarea id="paper" ng-model="paper"></textarea>
In my code I apply a $watch on this ng-model variable.
$scope.$watch("paper", onTextChange);
Everything is good except that onTextChange is fired not only when I type something, but when I click away from textareaб and also when I switch to another tab.
How can I prevent it so that onTextChange is fired only when the text is changed, meaning when I type in something or delete chars?
Demo with instructions: plunker
here's a fix:
http://plnkr.co/edit/kycmUrthYU38Ukdz0jJG?p=preview
setTimeout(
function() {
$scope.$watch("paper", function(newtext, oldtext) {
if (newtext !== oldtext) {
onTextChange();
}
});
}, 100)
So the issue is that watch fires the function whenever angularjs tells the app to digest. What you did was tell it to call the 'change' function EVERY time, when you should have passed in a checker function to check the change happened. It's about 'watching', not about 'watching for changes' - the function argument is supposed to see if you need to do something.
Extra note:
AngularJS sets up watchers for all kinds of things on various elements - here's a bit more info. I believe the blur corresponds to ng-touched which triggers the digest What gets added to $scope.$$watchers by default in Angular? And what triggers $digests?
It is a bad idea to use setTimeout. Instead, you could use ng-model-options="{ getterSetter: true }" and write a method to get/set the value (Modified get/set Plunk) and handle the text change condition within this method.

how to always use a 'set-method' to change the property of an object used as ng-model

in the controller I have:
$scope.foo = new Foo('test'); // foo.property now 'test'
in the view I have
<input type="text" ng-model="foo.property" />
But I don't really want foo.property to be changed directly.
I want it to be changed by calling a 'set method' for it, like
foo.setProperty('new-value');
So I know I could do something with watch like so:
$scope.$watch('myObject.property', function(newValue,oldValue) {
myObject.setProperty(newValue);
}
But I'll have this situation many times over again, so my question is if I can do this more automatically for every Foo?
or: How can I best implement something in Angular so that every time someFoo.property is used as a ng-model it is changed by calling someFoo.setProperty(newValue)?
You can call the setter method on blur event:
<input type type="text" ng-model="data.property" ng-blur="foo.setProperty(data.property)">
or use ng-change="foo.setProperty(data.property)"
You basically want the foo.property model to not be changed due to User Interaction, yes? Why don't you disable the input box using HTML, which will make sure no user interaction ever changes the input value, and then use $watch? $watch is the perfect way to handle what you require. And as long as it is not a deep watch, there's no performance concern either.

Intercepting all clicks with AngularJS to warn user of unsaved data

I have a lengthy form customers will need to fill out. If they click a link on a page, it will navigate away from that Controller and they will lose any data they may have already input.
If I can determine the form has not yet been saved, how can I intercept any click to the links on the page so I can ask the user if they want to save their form first?
No code yet- sorry. Many thanks.
I've written an angularjs directive that you can apply to any form that will automatically watch for changes and message the user if they reload the page or navigate away. #see https://github.com/facultymatt/angular-unsavedChanges
Hopefully you find this directive useful!
sorry for the late answer but mabye someone stumbles upon this and finds it useful. I have encountered the same problem and at the beginning i tryed to use the ng-dirty class applyed to the form element but because i had some custom controls the ng-bind won't be applyed when i changed some fields.
The best way i found so far is to detect when the model is changed with the use of $locationChangeStart event.
$scope.$on('$locationChangeStart', function (event, next, current) {
//we are about to leave the page so it's time to see if the form was modified by the user
if (!$scope.isFormClean())
{
event.preventDefault();
}
});

Resources