How to properly clean form with invalid input from AngularJS controller? - javascript

I have an AngularJS form that contains - among other fields - one of type url. The latter is important as this forces the corresponding input to be a valid URL.
Under certain conditions (for instance, a modal dialog with such a form is to be closed), I want to clear that form programmatically. For that purpose, I implemented method reset that basically clears the corresponding form model by setting $scope.formData = {}. Thus, it sets the form model to a new, blank object.
While that assignment clears all valid fields in the rendered HTML form, it does not clear invalid fields, like an invalid URL. For instance, if the user would provide invalid input ht://t/p as URL, that input would not be removed from the rendered form.
I think this is due to the fact that any invalid URL is not reflected by the model - such an invalid URL just wouldn't "make" it to the model because it does not pass validation in the NgModelController#$parsers array. Thus, in the model - there is no URL at all. Consequently, resetting the form model to {} cannot actually change the model's URL as it has not been set yet.
However, if method reset explicitly sets field $scope.formData.url = "", the invalid URL will be cleared properly (at least, the rendered form won't show it anymore). This is caused by the explicit change of the URL in the model. However, now, model variable formData.url contains the empty string (well, not surprisingly), while by using = {}, all fields would be undefined instead.
While assigning individual fields to "" works as workaround for simple forms, it quickly becomes cumbersome for more complex forms with many fields.
Thus, how could I programmatically reset the form efficiently and effectively - including all invalid input fields as well?
I created a Plunker at http://plnkr.co/c2Yhzs where you can examine and run a complete example showing the above effect.

Specify the type of your button as reset. That will not only call the ngClick function, it will also clear the content of the HTML form.
<button type="reset" ng-click="resetFormData()">Reset</button>

I think this solution is moderately elegant: your plnkr reviewed
The big difference is the initialization of your model object.
I think things gets messed up when a variable becomes undefined, it doesn't get updated anymore.. it should be connected (veeeery) deeply with how validation works (docs link)
Returning undefined in that case makes the model not get updated, i think this is exactly what happens behind the curtain
PS: you can recycle resetImplicitly for all your forms in the webapp :)

After trying several answers without success in similar questions, this worked for me.
In my controller:
$scope.cleanForm = function() {
$scope.myFormName.$rollbackViewValue();
};
Just call with some ng-click or any way you want.
Cheers

The Thing is tag is of type "url" which means
if user will enter specifically a valid url then only it will set values of model
If user will expicitly reset it which means setting model values to "" will again make textbox empty .
It is looking like it is setting the values but actually not ,so when you set its value to "" .Angular will set modal value to ""
Lets take another example : put replace "text" with "email"
<input type="email" ng-model="formData.name" />
<br />URL:
<input type="url" ng-model="formData.url" />
<br />
In above code If you will enter invalid email it will not set the values of email's model.

You probably need to make a copy of the model in its pristine state and set the model to pristine when you reset.
There's a good example here:
http://www.angularjshub.com/examples/forms/formreset/

The url form fields are passed into the model only if they are valid. Thus in case of an invlaid-url entry in the form, the scope variable is not assigned with the model and clearing the forms entry by assigning an empty object to the model will still persist the value at the UI front.
The best alternative to this is to assign the model associated with the form data with a null. A similar answer appears here:
https://stackoverflow.com/a/18874550/5065857

ng-click="formData={};"
just give like this ,
<button ng-click="formData={}">(1) Reset Full Data: formData = {}</button>
Reset your form data directly in ng-click itself.

Related

Angular Form Array - Dynamically Add Value to Nested Form Group

I followed the this video to create a reactive form to input data into an Angular application I am working on. It does what I want for the most part, however, I have added an additional control("setNumber") to be added in the reactive form array, but instead of inputting a value through the input fields of "name" and "gender", to enter it into the form I would like the value to auto-populate to the getUserForm group/submittable form automatically based on the iteration of the component .
I would ideally like it to display next to name and gender as well as be placed within the form
I put the code on StackBlitz here, where I just have setNumber(core>service>exerciseInput.service.ts) as its own input field and it does indeed update the form to be submitted...but for some reason there I am getting a type error on stackblitz that I am not getting in VSC. But its the code I am using and it works fine on my machine.
Application view
Anyway from stackblitz I believe I should be able to use property binding somewhere to pass i as a value into the component(not just the view as shown in the span string interpolation) so that the form automatically populates i as the setNumber within the userArray, but I've had no luck in my attempts over the last few days.
The fix should really be something incredibly easy I'm overlooking in the following block of code in input-array.component.html but I just cant get it to work.
<div *ngFor="let u of userArray.controls; index as i">
<span>Set {{ i + 1 }}</span>
<app-input [inputFormGroup]="$any(u)"></app-input>
<button (click)="removeUser(i)">Delete</button>
</div>
I would be incredibly grateful for any help!
Thank you
I'm trying to solve your problem and one of the solution is patching your FormArray via pipe, but I think there you need to implement ControlValueAccessor interface and make communication between parent and child Forms without mutation. Pay attention to IndexedFormPipe
Stackblitz
In your stackblitz, the type errors were fixed by adding form1: FormGroup in InputArrayComponent (I had to add types elsewhere too). Also, there were errors with regards to importing scss files that weren't there.
In any case, I may have misunderstood your question, but if you simply want to pass the value of i+1 to the input component and set that value to the FormControl of "setNumber", you simply add an Input() value to InputComponent (I've called it index):
#Input() index: number;
Then in the InputArrayComponent template I pass i+1 to the inputComponent
<app-input [index]="i+1" [inputFormGroup]="$any(u)"></app-input>
And in ngOninit() for the inputArray I assign the value:
this.inputFormGroup.get('setNumber').setValue(this.index);
Here's the stackblitz:
https://stackblitz.com/edit/angular-ivy-2rwlwu?file=src/app/views/input/input.component.html
--- EDIT ---
Your comment made clear an issue with deleting users – the index doesn't update. To have the index update automatically as users are deleted, you need to have the logic be performed any time the input changes, which can be done with a set function:
#Input() set i(value: number) {
this.index = value;
this.inputFormGroup.get('setNumber')?.setValue(value);
};
The issue with this is that it overwrites any changes to setNumber that the user may have made. There are ways around it, but my gut tells me it doesn't make sense for this value to be edited anyway.
So you could simply replace the input with a label that holds the value of index. The value will still appear in the form group (and in the submitted result), but there's no way for the user to edit it.
I've forked the stackblitz again with the differences:
https://stackblitz.com/edit/angular-ivy-hhhz9m?file=src/app/views/input/input.component.html

Validation not to submit an empty object with form-redux

Please see my example: https://codesandbox.io/s/qzm5q6xvx4
I can add and remove fields on my form. When initialised the form has the submission button disabled as it uses 'pristine' that looks for form changes.
The problem is, when I add a field and then remove that field so there are no fields remaining the submission button is available to submit even though there is not data added - it sees the form no longer as 'pristine' and the json output is:
{
"firstName": []
}
where ideally this needs to go back to undefined in order for the form to be pristine or I need some sort of check/validation on the submission. I thought these was one but cant see it. Prisite looks for json changes I think and I think this is where the problem is.
I am using react, redux-form, lodash.
So to solve my issue, I passed an empty object of firstName to the component -
initialValues={{firstName: []}
So the form now starts with the empty object therefore keeping the form pristine if a field gets created and then removed.
You do this by using the redux-form initialValues prop.
https://codesandbox.io/s/1o82695rr4

Object properties being wiped by fast changes

I have a form that uses server-side validation and coercion.
In Vue, the state of the form fields is held in an object called instance, on the data object. Each field's value is represented by a property of instance.
onChange of any field, instance is posted to an API method that returns validation results and a coerced dataset (coercion does things like adding spaces to phone numbers, capitalising postcodes etc.).
Vue takes the response and iterates through the coerced data, replacing the properties of instance. If a field has not yet been reached by the user it is skipped (There is a reached object that keeps track of which fields the user has made it to).
The issue that I'm having is that occasionally (when entering data extremely quickly from one field to the next) the input of the current field gets cleared when the coerced data is returned from the previous one.
Initially I thought that there must be some issue with the reached logic, and that the null data returned for the field that the user is working on is overwriting the current input. But this is not the case; I can see in my logs that fields are being skipped yet the input is still clearing.
I'm starting to think that this might be a bug with Vue. Or at least, something specific to how Vue handles the data/dom elements that I need to account for. Is there a way that setting instance.foo could cause instance.bar to be reset?
//this is called onChange for any field.
change: function(e) {
this.$set(this.instance, e.name, e.value);
this.setReached(e.name);
this.validate(true);
},
validate: function(reachedOnly) {
axios.post(this.validateUrl, this.getFormData(false)).then(response => {
this.allErrors = response.data.errors;
this.setFormData(response.data.values, reachedOnly);
this.fieldNumberValidated = this.fieldNumberReached;
});
},
setFormData: function(data, reachedOnly) {
for (var fieldName in this.fieldNames) {
var value = data[fieldName];
if(reachedOnly && !this.reached[fieldName]){
console.log('skipping - '+fieldName);
continue;
}
if (value && value.date) {
value = value.date.replace(/\.\d+$/,'');
}
this.$set(this.instance,fieldName,value);
}
},
* UPDATE: *
I think I know what's happening now.
Field A triggers change()
Data gets sent for validation
User starts inputting into field B
Validated data gets returned. And set on this.instance.
Vue skips field B because it isnt in this.reached
BUT this.instance is being updated and redrawn.
Field B may have text entered in its input but it hasn't been added to this.instance because it hasn't triggered change() yet. So this.instance is redrawn based on field B having no value, which in turn updates the input and wipes whatever may have been in there.
This isn't a full answer but just some thoughts.
I'm not certain about why a field is being cleared, however I would like to point out a concurrency issue you may have. If you're calling the API for each keypress, you're not guaranteed that they will respond in the correct order, and it could be that you are setting the form data to an old validation response which would cause you to lose any text entered into the textbox since the request was fired. Also it's generally a good idea not to spam the server with too many requests.
At a minimum you should probably debounce the API calls, or use blur instead of change event, or you could implement some logic that cancels any pending validation request before firing another one.
Is there any particular reason why you are using this.$set? It should only be used if you're adding a property to an object.
Initially I thought that there must be some issue with the reached logic, and that the null data returned for the field that the user is working on is overwriting the current input. But this is not the case; I can see in my logs that fields are being skipped yet the input is still clearing.
It might be better to log when you set the data, instead of when you skip. The issue is some fields are being cleared, so log every time they are set so you can identify times when the field is being set when it shouldn't be.
Is there a way that setting instance.foo could cause instance.bar to be reset?
Not that I'm aware of. It would help if you can provide a MCVE.
I eventually solved this by having 2 different events on my input fields - one for input that updates instance and another on blur that sends the validation request.
change: function(e) {
this.validate(true);
},
input: function(e) {
this.$set(this.instance, e.name, e.value);
},
This ensures that the properties of instance are always in line with their related input fields, and so nothing gets erased when instance is redrawn.

How can i identify and remove jsonObject empty properties without using for each

My requirement is,
By default submit button is in disable mode, If user changes existing
data in form then submit button should change to enable mode after
that he changes his mind and reverted back to old data then submit
button should change to disable mode.
For this requirement I have done code changes those are
While loading form data I am taking a temporary variable placing
form data in that temporary variable.
User changes any field in the form I am comparing form data with that temporary form data by using angular.equals(obj, tempObj)
method.
These code changes are working for one way i.e, User changes form data, I am identifying that event and comparing form data with temporary data and changing the button into enable mode --> working
The problem is my form contains non mandatory fields. By default form contains empty data in those non mandatory fields.
First user enters data in that non mandatory field then one json key, value pair is creating in my JsonObject. When i compare two objects by using equals method it is returning false after that i revert back that non mandatory field but the created json object property is not removing from JsonObject with empty value. This is angular inbuilt behavior.
Can anyone suggest me how to remove that empty value json key in my JsonObject.
Note : I will not use for each to iterate and find the empty json value and remove it from the JsonObject because my form contains 40 elements. It will leads the performance of my application. I have to apply same behavior in different places.
You can _.Omit to remove json object https://lodash.com/docs/4.17.10#omit

Force re-validation in jsViews if data-linked value doesn't change

I ran into an issue while using jsViews with validation (using code from jsviews.com/#samples) and jQuery UI Autocomplete:
I have a converter (convertBack) on the field that transforms the entered text back into a GUID based on a dictionary. It returns null if the field is empty or invalid.
The issue is that jsViews doesn't notice the update from one null value to the other (i.e. blank to invalid, and vice versa). I tried to fix this by adding a call to refreshValidates() on the validation tag to the DOM onChange manually, but any invalid value entered gets deleted.
Question: Is there a way to achieve re-validation in jsViews natively?
I changed the jsViews validation code to allow checking displayed value:
[End of onAfterLink]: It passes the current (displayed) value, not only the converted (which is null in both cases):
(...)
tag.validate(tagCtx.args[0], tag.linkedElem.val()); // Validate initial data
tag.linkedElem is the HTML element on which you are doing 2-way data-binding, (and validation).
So if you just want to get the current value of the input element, yes, tag.linkedElem.val() is good.
Additional response added following your comment below:
Looking at your jsfiddle: http://jsfiddle.net/w43hD/1/
You are using
data-link="{validate DictionaryValue inDictionary='dictionary' convert='fromGuid' convertBack='toGuid'}"
which will trigger validation when the DictionaryValue it is binding to changes. But you are mapping both invalid user entry strings and the empty string to the same DictionaryValue of null. So of course switching from invalid to empty string does not trigger validation.
You can change your getKey converter to map the empty string to something other than null, e.g. "" - and then it works. See updated version: http://jsfiddle.net/w43hD/2/

Categories

Resources