Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS form validation : $dirty value change through ui.router state changes

My use case :

I have a multi step form using ui-router like in the plunkr below. I use ng-form to validate information provided by AngularJS, like $valid, $dirty etc.

After each click on the "Next section" button, I send the form data to the server in order to retrieve it, in case the user quits the form before finishing.

If the user submits the first step twice, I ONLY send the edited data (if the $dirty value is true). All of this is not in the plunkr, I chose to show you a simple form, but my form can contain a hundred fields (radio, checkbox, input, select etc.).

The steps to reproduce the issue (plunkr) :

  1. Fill step 1 and go to next section
  2. Check xbox radio and return to step 1 by clicking on the number myMultiStepForm.interests.xbox.$dirty = true
  3. Come back to step 2 myMultiStepForm.interests.xbox.$dirty = false

Why is $dirty value changed to false? I guess it's because the <ng-form> is displayed again and all validation data is reset.

Is there a way to avoid this ? Or maybe something other than <ng-form> to handle validation of subsets of fields ?

This is the plunkr : http://plnkr.co/edit/WclqVgiBvUXlsGdSCcj0?p=preview

like image 600
tomahim Avatar asked Oct 07 '16 14:10

tomahim


2 Answers

When you link a form or any controller inside it, it always starts out as $pristine. The reason is that the models like formData.type simply have some values, angular has no way to know that those values were the default state, coming from the server, or are the result of previous user interaction; they are a simple string or something without this kind of metadata attached.

To achieve what you want, you have to manually track the $dirty state across state transitions, and apply $setDirty on the form when needed.

Here is a quick example, adding a controller to the form step pages, which saves the form state on exit (to a shared service instance, you could add this via resolve too) and restores it at construction. The current formPage is injected via a default parameter value so that the same controller can be used for all steps.

// router:

$stateProvider.state('form.interests', {
  url: '/interests',
  controller: 'FormStepController',  
  params: { formPage: 'interests'}
  templateUrl: 'form-interests.html'
})

// state

angular.value("formDirtyState", {});

// controller

angular.controller("FormStepController", function($scope, formDirtyState, $stateParams) {
  var formPage = stateParams.formPage;

  for(var formField in $scope.myMultiStepForm[formPage]) {
    if(formDirtyState[formPage] && formDirtyState[formPage][formField])
      $scope.myMultiStepForm[formPage][formField].$setDirty()
  }
    

  $scope.$on("$destroy", function() { 
    for(var formField in $scope.myMultiStepForm[formPage])
      formDirtyState[formField] = $scope.myMultiStepForm[formPage][formField].$dirty;
  })

})
like image 179
yscik Avatar answered Oct 30 '22 00:10

yscik


I solved and the solution is in the below plunker.

Referring to this :

Note: the purpose of ngForm is to group controls, but not to be a replacement for the tag with all of its capabilities (e.g. posting to the server, ...).

as per Angular Documentation for ngForm element.

Also I wonder why you are using many <ng-form> elements. This is the only possible solution for your problem.

Plunker Modified

Update 1:

Explanation for $dirty

$dirty is set to true only if the user interacted with that particular element in the current scope.

If you're so particular with true/false problem

  • Suggesting you to replace the $dirty to $pristine

Update 2 :

Why is the value not true when navigating again back

When you navigate from one form to another the scope of the ng-form of that particular element is added to the parent controller, when the same ng-form is visited again it overrides with that of the existing one.

like image 24
Aravind Avatar answered Oct 29 '22 23:10

Aravind