How to update a form controller in AngularJS after changing input constraints

I recently worked on dynamically changing constraints on input fields on a form. The goal was to create a reverse form while all the fields and their constraints are sent to us by the server. While I was able to get the add / remove constraints as needed by creating a simple directive, it looks like the form controller is not picking up those changes and therefore the form is always valid. Check out this jsfiddle , here is the directive:

myApp.directive('uiConstraints', [function(){

function applyConstraints(element, newVal, oldVal){
    //remove old constraints
    if(oldVal !== undefined && oldVal !== null){
        for (var i = 0; i < oldVal.length; i++) {
            element.removeAttr(oldVal[i].key);
        }
    }

    //apply new constraints
    if(newVal !== undefined && newVal !== null){
        for (var i = 0; i < newVal.length; i++) {
            var constraint = newVal[i];
            element.attr(constraint.key, constraint.value);
        }
    }
}

function link(scope, element, attrs){
    scope.$watch(attrs.uiConstraints, function(newVal, oldVal){
        applyConstraints(element, newVal, oldVal);
    });
}

return {
    restrict : 'A',
    link : link
};

}]);

      

The required behavior works like it does in the official angunkjs plunker . However, it seems that the FormController is created before the directive fills the constraints on the input fields, and updating those constraints does not update the corresponding values ​​in the FormController.

Does anyone know if I can get the ForceController to pick up the changes to the constraints made by the directive? And if so, how? I have no idea where to start ... Thanks.

- EDIT -
I couldn't get the plunker to work (show others my recent changes), so here is the jsfiddle of what I have: the last One goes into
more detail about the problem:

  • go to jsfiddle description
  • if you remove the original value from the textbox it turns red (invalid), however the controller will not select this and will show:

myform. $ valid = true
myform.myfield. $ valid = true

- EDIT - The
description of bounty does not recognize formatting (like 2 spaces for a newline, etc.), so here it is in a more readable form:

Since this is still an unresolved and interesting question, I decided to start the bounty.
Requirements:
- working on ancient AngularJS (1.0.3) and newer (if this cannot be done on 1.0.3, but someone did it on a newer version of angular, I am the reward for the reward)
- initially the field has no restrictions on it (not required, max and min are not set, etc.)
- the constraints for the field can be changed at any time (it may be required or the template for the value is set, etc.), and any existing constraints can be removed
- all constraints are stored in the controller in an object or array
- the FormController picks up the changes, so any value of $ scope.FormName. $ Valid is changed accordingly when constraints on any fields in this form are changed

My jsfiddle is a good starting point . Thanks for your time and good luck!

+3


source to share


4 answers


Check this PLUNK

.directive('uiConstraints', ["$compile",
function($compile) {
  function applyConstraints(element, newVal, oldVal) {
    //apply new constraints
    if (newVal !== undefined && newVal !== null) {
      for (var i = 0; i < newVal.length; i++) {
        var constraint = newVal[i];
        element.attr(constraint.key, constraint.value);
      }
    }
  }

  return {
    restrict: 'A',
    terminal: true,
    priority: 1000,
    require: "^form",
    link: function(scope, element, attrs, formController) {
      var templateElement;
      var previousTemplate;
      templateElement = element.clone(); //get the template element and store it 
      templateElement.removeAttr("ui-constraints");// remove the directive so that the next compile does not run this directive again
      previousTemplate = element;

      scope.$watch(attrs.uiConstraints, function(newVal, oldVal) {
        var clonedTemplate = templateElement.clone();
        applyConstraints(clonedTemplate, newVal);
        clonedTemplate.insertBefore(previousTemplate);

        var control = formController[previousTemplate.attr("name")];
        if (control){
           formController.$removeControl(control);
        }
        if (previousTemplate) {
          previousTemplate.remove();
        }

        $compile(clonedTemplate)(scope);
        previousTemplate = clonedTemplate;
      });
    }
  };
}]);

      

The idea here is to set terminal: true

and priority: 1000

have our directive compiled first and skip all other directives in the same element to get the template element . If you need to understand more, check my answer: Add directives from directive in AngularJS .



After getting the template element, I remove the directive ui-constraints

to avoid recompiling this directive, which added $watch

to the digest loop every time we switch constraints.

When the constraints change, I use this template item in to build a new item containing all the constraints without a directiveui-constraints

and compile it. Then I remove the previous element from the DOM and its controller from the form controller to avoid leaking and problems caused by the previous element controller existing in the form controller.

+2


source


For starters, AngularJS has a directive ng-required

that allows you to select the required validation

<input type="text" data-ng-required="isRequired" 
               data-ng-model="mydata" name="myfield" />

      

see the fiddle:



Fiddle

Creating the Min and Max logic is a little trickier. Your current JQuery approach is as in alter elements that are not AngularJS . The ideal answer is such a directive as ng-minlnegth

with great intelligence.

I will consider this.

+2


source


A quick and dirty solution:

scope.$watch(attrs.uiConstraints, function(newVal, oldVal){
    applyConstraints(element, newVal, oldVal);
    if (newVal != oldVal) {
        element = $compile(element)(scope);
    }            
});

      

I don't like the solution and it is not perfect. This is an interesting problem, so I am still working on finding a more elegant solution.

+1


source


Here's an example, sir:

http://plnkr.co/edit/QhimTgCqcaLsjIEvfZyS?p=preview

What went wrong for you is that you tried to use scope in your application and you didn't notice anything.

I changed your link function:

 link : function(scope, element, attrs){
            scope.$watch(attrs.constraints, function(newConstraints, oldConstraints){
              applyConstraints(newConstraints, oldConstraints, element);
            }, true);
        }

      

And your toggle function now does some random attributes and stuff:

$scope.toggleConstraints = function(myconstraints){
    var getRandomPropOrValueName = function(){
      return Math.random().toString(36).substring(7);
    }
    $scope.myconstraints = [{key: getRandomPropOrValueName(), value: getRandomPropOrValueName() }];
  };

      

0


source







All Articles