angularjsangular-ui-routerangularjs-ng-form

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


Solution

  • 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;
      })
    
    })